繼承
?
mixin混合繼承
function mixin(obj1, obj2) {for (var key in obj2) {//重復不復制if (!(key in obj1)) {obj1[key] = obj2[key];}}return obj1;}
這種復制是淺復制,對象或者數組函數等都是同一個引用,改變obj1的會同時影響obj2。
?
寄生繼承
...
?
隱式繼承
子類調用fn.call(this)
?
深拷貝需要重新聲明一個變量(對象),遍歷(遞歸)復制,詳情見我的函數技巧,不貼出來了。
?
?
原型
Javascript對象中有一個特殊的[[prototype]]內置屬性,其實就是對于其他對象的引用。幾乎所有的對象在創建時[[prototype]]屬性都會被賦予一個非空的值。
var f = {a: 1};// 創建一個對象 原型為fvar f2 = Object.create(f);// 通過原型鏈找到了屬性aconsole.log(f2.a);
使用for..in遍歷對象的原理和原型鏈類似,任意可枚舉在原型鏈上的屬性都會被遍歷。使用in操作符檢查屬性時也會查找對象原型鏈,無論是否可枚舉。
所有普通對象的原型最終指向Object.prototype。
詳細講解一個對象賦值語句:
var obj = {};obj.a = 1;
這里有四種情況:
1、obj中存在a屬性,就會被修改。
var obj = {a: 2};obj.a = 1;console.log(obj.a); //1
2、obj的原型鏈不存在a屬性,就會被直接添加到obj上。
var obj = {};console.log('a' in obj); //falseobj.a = 1;console.log(obj.a); //1
3、obj與obj的原型鏈都存在a屬性,就會發生屏蔽,obj中的a會屏蔽原型鏈上的a。
var obj2 = {a: 2};var obj = Object.create(obj2);obj.a = 1;console.log(obj.a); //1
4、obj的原型鏈上存在a屬性,而obj不存在時,會出現多種情況。
在原型鏈上存在a屬性且沒有被標記為只讀,那就會直接在obj添加一個a屬性。(情況3)
在原型鏈上存在a屬性且被標記為只讀,那么無法創建該屬性,嚴格模式會報錯,普通模式賦值語句會被忽略。
// 在'use strict'模式下// Cannot assign to read only property 'a' of object '#<Object>'var obj2 = {};Object.defineProperty(obj2, 'a', {value: 2,configurable: true,enumerable: true,writable: false})var obj = Object.create(obj2);obj.a = 1; //無效console.log(obj.a); //2
如果在原型鏈上存在a并且它是一個setter,那就一定會調用這個setter。a不會被添加到obj,也不會重新定義setter。
var obj2 = {set a(val) {console.log(1);}};var obj = Object.create(obj2);obj.a = 1; // 執行set并輸出1
如果希望怎么樣都添加屬性,請使用Object.defineProperty(...)。
?
關于prototype
所有函數默認都會擁有一個名為prototype的公有不可枚舉屬性,它會指向另外一個對象:
function fn() {console.log(1);}console.log(fn.prototype); //Object{}
?
這個對象通常被稱為fn的原型,實際上不如叫fn.prototype。
function fn() {console.log(1);}var f = new fn();console.log(f.__proto__ === fn.prototype); //true
在調用new fn()時,會創建一個對象,并給一個內部[[prototype]]鏈接,連接到fn.prototype。個人感覺__proto__這個瀏覽器私有實現的屬性叫原型比較好,畢竟原型鏈是通過這個屬性向上查找的。
實際上,new操作符實際上并沒有直接創建關聯,這只是一個副作用。
通過Object.create()方法才是正規創建原型鏈接的方法。
上一段代碼很容易讓人認為fn是一個構造函數,因為這里用new來調用它并構造出一個對象。
實際上,fn和普通的函數沒有區別。函數本身不是構造函數,當在普通的函數前面加上new時,就會把這個函數調用變成了一個‘構造函數調用’。實際上,new會劫持所有普通函數并用構造形式來調用它。
考慮下面一段代碼。
function fn(a) {this.a = a;}fn.prototype.getA = function() {return this.a;}var f1 = new fn(1);var f2 = new fn(2);console.log(f1.getA()); //1console.log(f2.getA()); //2
? 這段代碼展示了兩種面向類的技巧:
1、this.name=name給每個對象都綁定了.name屬性。
2、fn.prototype.getA=...給原型添加了一個方法,現在,每一個實例都可以調用getA方法。
看起來,似乎創建f1、f2時會把對象復制到這兩個新對象中,然而實際上只是通過原型鏈向上查找調用了方法而已。
?
關于constructor
function fn1() {};var f1 = new fn1();console.log(f1.constructor === fn1); //true//替換默認原型function fn2() {};fn2.prototype = {};var f = new fn2();console.log(f.constructor === fn2); //falseconsole.log(f.constructor === Object); //true
當前用新對象替換fn原型時,new出來的新對象不會自動獲得constructor屬性。所以,不能說因為f.constructor===fn屬性,就認為fn構造了對象f。
實際上,new出來的對象f并沒有.constructor屬性,它會委托原型去查找該屬性,默認的原型(fn.prototype)有construtor屬性并且指向fn,所以f.constructor(實際上調用的是fn.prototype.constructor)會指向fn。但是如果替換了fn.prototype,新的原型對象并不會默認有.construtor,于是委托會一直提交到Object.prototype,恰好Object.prototype.constructor===Object,結果也在上面代碼中展示出來了。
可以手動給新原型添加constructor屬性:
function fn2() {};fn2.prototype = {};fn2.prototype.constructor = fn2; //修正原型鏈var f = new fn2();console.log(f.constructor === fn2); //trueconsole.log(f.constructor === Object); //false
看,修復了!(實際上應該用Object.defineProperty來定義constructor,因為該屬性應該是不可枚舉的)
所以說,constructor并不是一個不可變屬性,它只是默認不可枚舉,但是值可以被任意修改。
?
原型繼承
常見誤用形式和正確使用方式:
function fn1() {};function fn2() {};//不可以 只是復制引用//fn1.prototype = fn2.prototype;//可以實現 但是會執行fn2函數 可能出現額外問題//fn1.prototype=new fn2;//ES6前 需要拋棄fn1默認的prototype 可能還要修正constructor屬性fn1.prototype = Object.create(fn2.prototype);//ES6語法 直接修正默認prototypeObject.setPrototypeOf(fn1.prototype, fn2.prototype);
如何找出任意對象的原型鏈呢?有一個方法是instanceof。
function fn() {}var f = new fn;console.log(f instanceof fn); //true
instanceof操作符左邊是一個對象,右邊是一個函數。該操作解決的問題是:在f的原型鏈上,是否有fn.prototype對象?(通過bind強綁生成的函數沒有prototype屬性)
如果要直接判斷兩個對象是否存在原型關系,可以用以下幾個方法:
function fn() {}var f = new fn;//是否是原型關系console.log(fn.prototype.isPrototypeOf(f)); //true//展示原型console.log(Object.getPrototypeOf(f)); //Object{}//瀏覽器私有實現console.log(f.__proto__); //Object{}
絕大多數瀏覽器支持__proto__方法來訪問[[prototype]]屬性。(__開頭的屬性表明這不是ECMA標準,還有很多其他的屬性也以__開頭)
現在ES6可以用Object.getPrototypeOf()與Object.setPropertyOf()來獲取和設置原型,相當于原生支持了__proto__。
Object.create()會創建一個對象,并關聯到參數對象中,避免了new操作符與生成對應的constructor,prototype。
如果舊瀏覽器不支持,可以用下面的代碼模擬:
if (!Object.create) {Object.create = function(o) {function f() {};f.prototype = o;return new f();}}
關于new操作符和原型,如果下面的代碼可以理解,那就沒問題了~
function fn(a) {this.a = a;}fn.prototype = {};Object.defineProperty(fn.prototype, 'a', {value: 1,configurable: true,enumerable: true,writable: false});//嚴格模式new會報錯var f = new fn(3);console.log(f); //無效!console.log(f.a); //1
完結撒花!
?