一盆顾、理解js的原型
1畏梆、函數(shù)和對象的關(guān)系
函數(shù)也是對象的一種,通過instanceof可以判斷出來宪巨。但是函數(shù)和對象的關(guān)系并不是簡單的包含和被包含的關(guān)系
對象都是通過函數(shù)創(chuàng)建的
var obj = {a:10,b:20};
var arr = [5, 'x', true];
但是溜畅,其實(shí)上面這段代碼的實(shí)質(zhì)是下面這樣的:
//var obj = { a: 10, b: 20 };
//var arr = [5, 'x', true];
var obj = new Object();
obj.a = 10;
obj.b = 20;
var arr = new Array();
arr[0] = 5;
arr[1] = 'x';
arr[2] = true;
而Object和Array都是函數(shù)捏卓,可以自己用typeof函數(shù)進(jìn)行驗(yàn)證怠晴。
所以浴捆,可以得出:對象都是通過函數(shù)創(chuàng)建的
2、原型prototype
在前言中冲粤,我們說了函數(shù)也是一種對象,所以函數(shù)也是屬性的集合色解,同時(shí)科阎,也可以對函數(shù)進(jìn)行自定義屬性忿族。
每個(gè)函數(shù)都有一個(gè)屬性——prototype。這個(gè)prototype的屬性值是一個(gè)對象(屬性的集合)错英,默認(rèn)只有一個(gè)叫做constructor的屬性隆豹,指向這個(gè)函數(shù)本身。 如下圖所示:
上圖中判哥,SuperType是一個(gè)函數(shù)塌计,右側(cè)的方框就是它的原型侯谁。
原型既然作為對象(屬性的集合),除了constructor外热芹,還可以自定義許多屬性惨撇,比如下面這樣的:
當(dāng)然了串纺,我們也可以在自己定義的方法的prototype中增加我們自己的屬性,比如像下面這樣的:
function Fn() { }
Fn.prototype.name = '張三';
Fn.prototype.getAge = function () {
return 12;
};
那么問題來了:函數(shù)的prototype到底有何用呢?
在解決這個(gè)問題之前祷蝌,我們還是先來看下另一個(gè)讓人迷糊的屬性:_proto_
3、“隱式原型”proto
我們先看一段非常常見的代碼:
function Fn() { }
Fn.prototype.name = '張三';
Fn.prototype.getAge = function () {
return 12;
};
var fn = new Fn();
console.log(fn.name);
console.log(fn.getAge ());
即米丘,F(xiàn)n是一個(gè)函數(shù)拄查,fn對象是從Fn函數(shù)new出來的,這樣fn對象就可以調(diào)用Fn.prototype中的屬性碍脏。
但是稍算,因?yàn)槊總€(gè)對象都有一個(gè)隱藏的屬性——“proto”,這個(gè)屬性引用了創(chuàng)建這個(gè)對象的函數(shù)的prototype钾埂。即:fn.proto === Fn.prototype
那么科平,這里的proto到底是什么呢匠抗?
其實(shí),這個(gè)proto是一個(gè)隱藏的屬性绳军,javascript不希望開發(fā)者用到這個(gè)屬性值矢腻,有的低版本瀏覽器甚至不支持這個(gè)屬性值。
var obj = {};
console.log(obj.__proto__);
console.log(Object.prototype);
從上面來看,obj.proto和Object.prototype的屬性一樣竣灌!為什么呢初嘹?
原因是:obj這個(gè)對象本質(zhì)上是被Object函數(shù)創(chuàng)建的,因此obj.proto=== Object.prototype坷随。我們可以用一個(gè)圖來表示。
即缸匪,每個(gè)對象都有一個(gè)proto屬性类溢,指向創(chuàng)建該對象的函數(shù)的prototype豌骏。
說一下自定義函數(shù)的prototype:
自定義函數(shù)的prototype本質(zhì)上就是和 var obj = {} 是一樣的,都是被Object創(chuàng)建,所以它的proto指向的就是Object.prototype钦睡。
但是荞怒,Object.prototype確實(shí)一個(gè)特例——它的proto指向的是null。
另外一個(gè)問題:函數(shù)也是一種對象衰抑,函數(shù)也有proto嗎荧嵌?
答:當(dāng)然也不例外啦啦撮!
下面用一段代碼和一張圖來說明這個(gè)問題,看完相信就有個(gè)比較直觀的理解啦愉择!
function fn(x, y) {
return x+y;
}
console.log(fn(10,20));
//以下只是為了演示函數(shù)被Function創(chuàng)建的
var fn1 = new Function("x","y","return x+y;");
console.log(fn1(5,6));
用圖表示就是:
從上圖可以看出:自定義函數(shù)Foo.proto指向Function.prototype织中,Object.proto指向Function.prototype狭吼。
但是,為什么有Function.proto指向Function.prototype呢窿春?
其實(shí)原因很簡單:Function也是一個(gè)函數(shù),函數(shù)是一種對象蔚润,也有proto屬性尺栖。既然是函數(shù),那么它一定是被Function創(chuàng)建除盏。所以Function是被自身創(chuàng)建的挫以。所以它的proto指向了自身的Prototype
最后一個(gè)問題:Function.prototype指向的對象掐松,它的proto是不是也指向Object.prototype?
答案是肯定的抡句。因?yàn)镕unction.prototype指向的對象也是一個(gè)普通的被Object創(chuàng)建的對象杠愧,所以也遵循基本的規(guī)則。如下圖:
說了這么多锐锣,我們將上面這些圖片整合到一整個(gè)圖片刺下,便于整體理解稽荧,圖片如下:
4姨丈、instanceof
主要是說明下instanceof的判斷規(guī)則是如何進(jìn)行的。先看如下代碼和圖片:
function fn() {
}
var f1 = new fn();
console.log(f1 instanceof fn);//true
console.log(f1 instanceof Object);//true
instanceof的判斷規(guī)則為:
假設(shè)instanceof運(yùn)算符的第一個(gè)變量是一個(gè)對象翁潘,暫時(shí)稱為A拜马;第二個(gè)變量一般是一個(gè)函數(shù),暫時(shí)稱為B旺坠。
instanceof的判斷規(guī)則是:沿著A的proto這條線來找扮超,同時(shí)沿著B的prototype這條線來找,如果兩條線能找到同一個(gè)引用璧疗,即同一個(gè)對象馁龟,那么就返回true恭垦。如果找到終點(diǎn)還未重合淌喻,則返回false裸删。
結(jié)合這個(gè)判斷規(guī)則阵赠,上面的代碼和圖示相信很容易看懂了。
二匕荸、原型繼承
原型鏈的定義:訪問一個(gè)對象的屬性時(shí)枷邪,先在基本屬性中查找东揣,如果沒有,再沿著proto這條鏈向上找尔觉,這就是原型鏈芥吟。
示例:在實(shí)際應(yīng)用中如何區(qū)分一個(gè)屬性到底是基本的還是從原型中找到的呢专甩?
答案就是:hasOwnProperty這個(gè)函數(shù)涤躲,特別是在for…in…循環(huán)中嫁盲,一定要注意。
但是8淄小俐镐!f1本身并沒有hasOwnProperty這個(gè)方法哺哼,那是從哪里來的呢取董?答案很簡單,是從Object.prototype中來的枢里□逦纾看下圖:
對象的原型鏈?zhǔn)茄刂?strong>proto這條線走的,因此在查找f1.hasOwnProperty屬性時(shí)奥洼,就會(huì)順著原型鏈一直查找到Object.prototype灵奖。
由于所有對象的原型鏈都會(huì)找到Object.prototype搬泥,因此所有對象都會(huì)有Object.prototype的方法。這就是所謂的“繼承”尉尾。
三燥透、原型繼承的幾種方式
- 原型鏈繼承
- 構(gòu)造函數(shù)繼承(對象冒充繼承)
- 組合繼承(原型鏈繼承+構(gòu)造函數(shù)繼承)
- 原型式繼承
- 寄生組合式繼承
1、原型鏈繼承
function Show(){
this.name="run";
}
function Run(){
this.age="20"; //Run繼承了Show,通過原型故河,形成鏈條
}
Run.prototype=new Show();
var show=new Run();
console.log(show.name)//結(jié)果:run
2吆豹、構(gòu)造函數(shù)繼承(對象冒充繼承)
為了解決引用共享和超類型無法傳參的問題,我們采用一種叫借用構(gòu)造函數(shù)的技術(shù)凑阶,或者成為對象冒充(偽造對象衷快、經(jīng)典繼承)的技術(shù)來解決這兩種問題
function Box(age){
this.name=['Lee','Jack','Hello']
this.age=age;
}
function Desk(age){
Box.call(this,age); //對象冒充蘸拔,給超類型傳參
}
var desk = new Desk(200);
console.log(desk.age);//200
console.log(desk.name);//['Lee','Jack','Hello']
desk.name.push('AAA'); //添加的新數(shù)據(jù),只給 desk
console.log(desk.name)//['Lee','Jack','Hello','AAA']
3宝冕、組合繼承(原型鏈繼承+構(gòu)造函數(shù)繼承)
借用構(gòu)造函數(shù)雖然解決了剛才兩種問題邓萨, 但沒有原型先誉, 復(fù)用則無從談起的烁。 所以渴庆, 我們需要原型鏈+借用構(gòu)造函數(shù)的模式,這種模式成為組合繼承刃滓。
function Box(age) {
this.name = ['Lee', 'Jack', 'Hello']
this.age = age;
}
Box.prototype.run = function () {
return this.name + this.age;
};
function Desk(age) {
Box.call(this, age); //對象冒充
}
Desk.prototype = new Box(); //原型鏈繼承
var desk = new Desk(100);
console.log(desk.run()); //Lee,Jack,Hello100
4耸弄、原型式繼承
這種繼承借助原型并基于已有的對象創(chuàng)建新對象计呈,
同時(shí)還不必因此創(chuàng)建自定義類型
function obj(o) { //傳遞一個(gè)字面量函數(shù)
function F() {} //創(chuàng)建一個(gè)構(gòu)造函數(shù)
F.prototype = o; //把字面量函數(shù)賦值給構(gòu)造函數(shù)的原型
return new F(); //最終返回出實(shí)例化的構(gòu)造函數(shù)
}
var box = { //字面量對象
name : 'Lee',
arr : ['哥哥','妹妹','姐姐']
};
var box1 = obj(box); //傳遞
console.log(box1.name); // Lee
box1.name = 'Jack';
console.log(box1.name); // Jack
console.log(box1.arr); // (3) ["哥哥", "妹妹", "姐姐"]
box1.arr.push('父母');
console.log(box1.arr); // (4) ["哥哥", "妹妹", "姐姐", "父母"]
var box2 = obj(box); //傳遞
console.log(box2.name); // Lee
console.log(box2.arr); //引用類型共享了 (4) ["哥哥", "妹妹", "姐姐", "父母"]
5征唬、寄生組合式繼承
寄生組合式繼承解決了兩次調(diào)用的問題茁彭,組合式繼承就會(huì)有兩次調(diào)用的情況
基本模型如下:
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function inheritPrototype(subType, superType) {
var prototype = object(superType.prototype); //創(chuàng)建對象
prototype.constructor = subType; //增強(qiáng)對象
subType.prototype = prototype; //指定對象
}