- 顯式原型和隱式原型,原型鏈。
顯式原型:prototype,隱式原型:__proto__在js中萬物皆物件,方法(Function)是物件,方法的原型(Function.prototype)是物件,物件具有屬性(__proto__)稱為隱式原型,物件的隱式原型指向構造該物件的建構函式的顯式原型。
方法(Function)是一個特殊的物件,除了和其他物件一樣具有__proto__屬性以外,它還有一個自己特有的原型屬性(prototype),這個屬性是一個指標,指向原型物件。原型物件也有一個屬性叫constructor,這個屬性包含一個指標,指向原建構函式。
顯示原型和隱式原型的關係:隱式原型指向建立這個物件的函式的prototype,建立物件的三中物件:
a.通過物件字面量的方式。
var person={
name:"Tom"
}複製程式碼
b.通過new的方式建立
//建立一個建構函式
function person(name){
this.name=name
}
//建立一個建構函式的例項
var person1=new person;複製程式碼
c.通過Object.creat()方式建立
但是本質上3種方法都是通過new的方式建立的。
其中通過Object.creat(o)建立出來的物件他的隱式原型指向o。
通過物件字面量的方式建立的物件他的隱式原型指向Object.prototype。
建構函式function person本質上是由Function建構函式建立的,它是Function的一個例項。原型物件本質上是由Object建構函式建立的。內建函式Array Number等也是有Function建構函式建立的。
因此也就不難理解下面幾個例子:
//通過new的方式
person1.__proto__===person.prototype //true
person.prototype.__proto__===Object.prototype //true
Object.__proto__===Function.prototype //true
//內建函式
Array.__proto__===Function.prototype //true
Array.prototype.__proto__===Object.prototype //true複製程式碼
- javascript建立物件的幾種方式
1.工廠模式2.建構函式模式
與工廠模式相比:
1、沒有顯式的建立物件
2、直接將屬性和方法賦給了this物件
3、沒有return語句
要建立person的例項,必須使用new操作符,以這種方式呼叫建構函式實際上會經歷4個步驟:
1、建立一個新物件
2、將建構函式的作用域賦給新物件
3、執行建構函式中的程式碼
4、返回新物件
建立自定義的建構函式可以將它的例項標識為一種特定的型別。
建構函式的缺點:
每個方法都有在每個例項上重新建立一遍。person1和person2都有一個sayName()的方法,但兩個方法不是同一個Function例項。不同例項上的同名函式是不相等的。
建立兩個完成同樣任務的Function例項沒有必要,而且還有this物件在,不需要在執行程式碼前就把函式繫結在特定物件上,可以像下面這樣。
把sayName屬性設定成全域性的sayName函式,這樣,由於sayName包含的是一個指向函式的指標,因此person1和person2物件就共享了同一個函式。
但是,如果物件需要定義很多方法,那麼就要定義很多全域性函式,自定義的引用型別也沒有封裝可言了。為了解決上述問題,引入原型模式。
3.原型模式
理解原型物件 (關於原型上文有講)
我們建立的每個函式都有一個prototype屬性,這個屬性是一個指標,指向一個物件,而這個物件的用途是包含可以由特定型別的所有例項共享的屬性和方法。prototype是通過呼叫建構函式而建立的那個物件例項的物件原型,使用原型物件的好處是可以讓所有物件例項共享它所包含的屬性和方法。
首先,解析器會問例項person1是否有name屬性,如果有,就返回。 如果沒有,就繼續去person1的原型中搜尋name屬性,如果有就返回。 如果沒有,再繼續向person1的原型的原型中搜尋。
isPrototypeOf()確定例項和原型物件之間的關聯
console.log(Person.prototype.isPrototypeOf(person1)); //true
Object.getPrototypeOf()返回的是[[prototype]]的值
console.log(Object.getPrototypeOf(person1));
//Person {name: “Yvette”, age: 26, job: “engineer”} 返回的是Person的原型物件。
console.log(Object.getPrototypeOf(person1) === Person.prototype)//true
console.log(Object.getPrototypeOf(person1).name);//”Yvette”
hasOwnProperty()方法可以檢測一個屬性是存在於例項中,還是存在於原型中,只有給定屬性存在於例項中,才會返回true。
console.log(person1.hasOwnProperty(“name”));//false
原型簡寫
這導致了person1.constructor不再指向Person,而是指向了Object。如果constructor很重要,則需要特意將其設為適當的值,如:
但是這種方式會導致constructor屬性變成可列舉。
如果想設定為不可列舉的(預設不可列舉),可以使用
Object.defineProperty(Person.prototype, “constructor”, {
enumerable: false,
value: Person
}); 複製程式碼
原型的動態性
由於在原型中查詢值的過程是一次搜尋,因此我們對原型物件所做的任何修改都能夠立即從例項上反映出來。
如果重寫整個原型物件,情況就不一樣了。呼叫建構函式時會為例項新增一個指向最初原型的[[prototype]]指標,而把原型修改為另外一個物件就等於切斷了建構函式與最初原型之間的聯絡。例項中的指標僅指向原型,而不指向建構函式。
person.prototype指向的是原本的原型物件,而不會指向新的原型物件。
原型物件的問題
原型模式最大問題是由其共享的本性所導致的。 對於包含引用型別值的屬性來說,問題較為突出
本意只想修改person1的friends,但是卻導致person2的friends屬性值也改變了。因此我們很少單獨使用原型模式。
4.組合使用構造模式和原型模式
建立自定義型別的最常用的方式,就是組合使用建構函式模式與原型模式。建構函式模式用於定義例項屬性,原型模式用於定義方法和共享的屬性,這樣每個例項都有自己的一份例項屬性的副本,又同時共享著對方法的引用,最大限度的節省了記憶體。
除了以上幾種方式以外,另外還有動態原型模式,寄生構造模式和穩妥構造模式,但是鑑於使用頻率較低,不再贅述。
- 實現繼承的多種方式和優缺點
1.prototype
//父類
function person(){
this.hair = 'black';
this.eye = 'black';
this.skin = 'yellow';
this.view = function(){
return this.hair + ',' + this.eye + ',' + this.skin;
}
}
//子類
function man(){
this.feature = ['beard','strong'];
}
man.prototype = new person();
var one = new man();
console.log(one.feature); //['beard','strong']
console.log(one.hair); //black
console.log(one.eye); //black
console.log(one.skin); //yellow
console.log(one.view()); //black,black,yellow
複製程式碼
這種方式最為簡單,只需要讓子類的prototype屬性值賦值為被繼承的一個例項就行了,之後就可以直接使用被繼承類的方法了。
2.建構函式(call)
let Super = function(name) {
this.name = name;
this.getName = () => {
return this.name;
}
}
let Sub = function(sex,name) {
Super.call(this,name); // 呼叫父類方法為子類例項新增屬性
this.sex = sex;
}
let sub1 = new Sub('male','eric'),
sub2 = new Sub('female','eric');
sub1.name = 'ada';
console.log(sub2.name); // eric,例項的屬性沒有相互影響
console.log(sub1.getName === sub2.getName); // false,可見方法沒有複用複製程式碼
優點:子類的每個例項都有自己的屬性(name),不會相互影響。
缺點:但是繼承父類方法的時候就不需要這種特性,沒有實現父類方法的複用。
3.組合式(call+prototype)
let Super = function(name) {
this.name = name;
}
Super.prototype = {
constructor: Super, // 保持建構函式和原型物件的完整性
getName() {
return this.name;
}
}
let Sub = function(sex) {
Super.call(this,'eric'); //繼承父類屬性
this.sex = sex;
}
Sub.prototype = new Super('eric'); //繼承父類方法
Sub.prototype.constructor = Sub;
let sub1 = new Sub('male'),
sub2 = new Sub('female');
// 可以按上述兩種方法驗證,複用了父類的方法,例項沒有複用,達到目的複製程式碼
優點:繼承了上述兩種方式的優點,摒棄了缺點,複用了方法,子類又有各自的屬性。
缺點:因為父類建構函式被執行了兩次,子類的原型物件(Sub.prototype)中也有一份父類的例項屬性,而且這些屬性會被子類例項(sub1,sub2)的屬性覆蓋掉,也存在記憶體浪費。
- 匿名函式
在Javascript定義一個函式一般有如下三種方式:
函式關鍵字(function)語句:
function fnMethodName(x){alert(x);} 複製程式碼
函式字面量(Function Literals):
var fnMethodName = function(x){alert(x);} 複製程式碼
Function()建構函式:
var fnMethodName = new Function(‘x’,’alert(x);’) 複製程式碼
上面三種方法定義了同一個方法函式fnMethodName,
第1種就是最常用的方法,後兩種都是把一個函式複製給變數fnMethodName,而這個函式是沒有名字的,即匿名函式。匿名函式可以簡單的理解為沒有名字的函式,匿名自執行函式可以簡單理解為可以自己執行的匿名函式 。
匿名函式的好處:匿名函式可以有效的保證在頁面上寫入Javascript,而不會造成全域性變數的汙染。 這在給一個不是很熟悉的頁面增加Javascript時非常有效,也很優美。
匿名函式的常見場景:
<input type="button" value="點選" id="btn"> <script type="text/javascript">
//匿名函式的第一種情形
var btn=document.querySelector("#btn");
btn.onclick=function(){ // alert("aaaaa"); }
//匿名函式的第二種情形
setInterval(function(){ // alert("bbbbb"); }, 1000);
//匿名函式的第三種情形
var fun=function(){ alert("ccccc"); } // fun();
//匿名函式的第四種情形
var obj={ name:"dddd", say:function(){ alert(this.name); } }
obj.say();
</script> 複製程式碼
匿名自執行函式,匿名自執行函式首先是一個匿名函式,但是這個函式是可以自己自動執行的,不需要藉助其他的元素。使用匿名自執行函式將某些程式碼包裹起來可以實現塊級作用域的效果,減少全域性變數的數量,在匿名自執行函式執行結束後變數就會被記憶體釋放掉,從而也會節省了記憶體。
<input type="button" value="點選" id="btn">
lt;script type="text/javascript"> //1,匿名函式的第一種實現方式
(function(data){ // alert(data); })("eee"); //2.匿名自執行函式的第二種實現方式
(function(){ // alert("fff"); }()); //3.匿名自執行函式的第三種實現方式
!function(data){ // alert(data); }("hhh"); //4.匿名自執行函式的第四種實現方式
var fun=function(data){ alert(data); }("iii"); 複製程式碼