JS 構(gòu)造函數(shù),原型對象和實例對象
直接看一幅圖:

看來這張圖和圖片上的代碼,對這些概念有了一個基本的了解,在看看下面的一段代碼:
function Person (name) {
this.name = name;
}
Person.prototype.sex = 'boy';
Person.prototype.sayWords = function(des) {
console.log(des+' say words:', this.name,this.sex);
}
let p1 = new Person('p1');
// code 1
p1.sayWords('p1-des1');
console.log("Person實例對象:p1(改變前):",p1);
console.log("Person原型對象:p1.__proto__:",Object.getPrototypeOf(p1));
// code 2
console.log("----------------------")
p1.name = 'p1-1001';
p1.sex = 'girl';
p1.sayWords('p1-des2');
console.log("Person實例對象:p1(改變后):",p1);
console.log("Person原型對象:p1.__proto__:",Object.getPrototypeOf(p1));
結(jié)果如下圖:

加了code 2部分代碼后:

從結(jié)果從可以看出,下面兩句代碼:
p1.name = 'p1-1001';
p1.sex = 'girl';
并沒有修改prototype原型對象里面的屬性sex的值,原型對象里面屬性sex依然是sex:"boy";這兩句代碼只是對實例對象本身做了修改,把實例對象的name值改為“girl",并且添加了一個sex屬性。
從這里就看出來了,寫在Person()構(gòu)造函數(shù)內(nèi)部的那些屬性或方法在new操作的過程中賦值給了實例對象p1的,所以構(gòu)造函數(shù)內(nèi)部的屬性方法都是當前實例對象所私有的。
new操作的時候,除了會指向構(gòu)造函數(shù)里面的代碼并返回一個實例對象(就是構(gòu)造函數(shù)里面的哪個this)外,還會把構(gòu)造函數(shù)的作用域賦值給創(chuàng)建的實例對象,所以構(gòu)造函數(shù)里面的this.xx = ‘xxx' 都是給實例對象添加屬性并賦值,好比這樣:
function PersonFn () {
let o = new Object();
o.a = 'aaa';
o.b = 'bbb';
return o;
}
每次執(zhí)行PersonFn 都會生成一個全新的對象o。
那么code 1那段代碼,p1實例對象是怎么拿到原型對象的sex屬性的呢?或者說實例對象是怎么訪問到原型對象的屬性和方法的呢?
答案就是原型鏈。
當我們new一個函數(shù)的時候,這個函數(shù)就從一個普通函數(shù)變成了構(gòu)造函數(shù),并且會返回一個實例對象,函數(shù)中的this自動指向了這個實例對象,所以構(gòu)造函數(shù)中的方法和屬性,用面向?qū)ο蟮恼Z言來說,構(gòu)造函數(shù)里面的屬性方法都是實例對象自己的私有成員,上面也說過了;同時實例對象還會有一個隱藏屬性叫做[[prototype]]或proto,這個屬性指向的就是原型對象,這個隱藏屬性就是實現(xiàn)原型鏈的關(guān)鍵。
還以code 1代碼段為例子,p1對象{”name":"p1",proto:原型對象地址},里面更本沒有sex這個屬性,也沒有sayWords這個方法,如果是一般的對象比如:
var px = {};
px.a;
px.fn();
這樣的一段代碼肯定會報錯的,px.a是undefined,px.fn()沒這個方法,直接報錯。
但是實例對象不會,當代碼執(zhí)行到p1.sayWords()的時候,如果實例對象自己沒有這個方法,代碼就會通過proto屬性,像攀爬鎖鏈一樣,繼續(xù)往上走,走到Person原型對象,原型對象這里是有這個方法的,于是就調(diào)用原型對象的這個方法。
再想想,如果Person原型對象也沒找到想要的屬性方法呢?仔細看看Person原型對象,它也有一個proto屬性,這個屬性指向的是Object函數(shù)的原型對象,同理,代碼依然會順著Person原型對象的proto屬性找到Object原型對象,如果Object原型對象也沒找到,那么就會報錯,因為Object是所有函數(shù)的父類,也鎖鏈的最頂層,找不到只能報錯了。
說起原型鏈,又想到作用域鏈。
作用域鏈也是類似的,比如:
<script>
var x = 0;
function a (){
x++;
}
a();
</script>
這是一段很簡單的代碼,如果a函數(shù)的作用域中沒有變量x,代碼就會繼續(xù)往上層作用域查找,直到找到變量x或者一直找到頂層作用域,即window對象,如果window對象中也沒有要查找的變量,就會報錯。
再說個方法:某個對象.hasOwnProperty("屬性"),判斷某個對象下面是否有某個屬性,如果屬性是原型對象里面的或者屬性不存在,都會返回false。
最后,再看一段代碼:
function Person (name) {
this.name = name;
}
Person.prototype.sex = 'boy';
Person.prototype.friendsArr = ['tom','lucy'];
Person.prototype.looksLikeObj = {
hair: "long black",
eyes: "blue"
};
Person.prototype.sayWords = function(des) {
console.log(des+' say words:', this.name,this.sex, this.looksLikeObj, this.friendsArr);
}
let p1 = new Person('p1');
p1.sayWords('p1-des1');
console.log("Person實例對象:p1(改變前):",p1);
console.log("Person原型對象:p1.__proto__:",Object.getPrototypeOf(p1));
console.log("----------------------")
p1.name = 'p1-1001';
p1.sex = 'girl';
p1.friendsArr.push('lilei');
p1.looksLikeObj['cloth'] = "skirt";
// p1.xyz['a'] = 'xyza'; // xyz對象不存在,Cannot set property 'a' of undefined
p1.sayWords('p1-des2');
console.log("Person實例對象:p1(改變后):",p1);
console.log("Person原型對象:p1.__proto__:",Object.getPrototypeOf(p1));
結(jié)果截圖:

對比一下,如果通過實例對象設(shè)置一個引用類型的值,如果實例對象本身不存在該屬性,而原型對象存在,它會直接修改共享的公共原型對象,從而影響到其他實例對象。
所以,除非你需要這樣的效果,否則,盡量不要將引用類型的屬性設(shè)置在原型對象中,可以考慮放到構(gòu)造函數(shù)中設(shè)置引用類型的屬性。