破解 JS(原型)繼承
總體分為四大類:利用空對象作為中介繼承、Object.create 繼承、setPrototypeOf 繼承、拷貝繼承
function Animal(name, age) { this.name = name; this.age = age; } Animal.prototype = { speak: function() { console.log(‘my name is ‘ + this.name); } } function Cat() { Animal.apply(this, arguments); this.food = ‘mouse‘; }
一、利用空對象作為中介繼承
function extend(child, parent) { var F = function() {}; F.prototype = parent.prototype; child.prototype = new F(); child.prototype.constructor = child; }
F是空對象,所以幾乎不占內存。這其實就是 YUI 實現繼承的方法。
試一試
二、Object.create 繼承
Object.create 會使用指定的原型對象和屬性去創建一個新對象。
function extend(child, parent) {// 任何一個prototype對象都有一個constructor屬性,指向它的構造函數。 // 使 Cat.prototype 指向 Animal.prototype, 但他有一個副作用:Cat.prototype.constructor指向Animal child.prototype = Object.create(parent.prototype); // 修正 constructor child.prototype.constructor = child; }
試一試
疑問一:為什麽不直接 child.prototype = parent.prototype; ?
如果這樣的話,child.prototype 會直接引用 parent.prototype 對象,那麽當你對 child.prototype.constructor 進行賦值操作時,就把 parent.prototype.constructor 也給修改了
疑問二:為什麽不用child.prototype = new parent(); ?
new parent() 確實會創建一個關聯到 child.prototype 的新對象。但如果函數 parent 有一些副作用(比如修改狀態、註冊到其它對象、給 this 添加屬性等等)的話,會影響到 child() 的後代,後果不堪設想!
綜上所訴,Object.create 是最好的選擇,雖然它是創建了一個新對象替換掉了默認的對象。那有沒有直接修改默認對象的方法呢?答案就是 setPrototypeOf
三、setPrototypeOf 繼承
setPrototypeOf 是 ES6新增的輔助函數。下面來做一下對比
// 拋棄默認的 child.prototype child.prototype = Object.create(parent.prototype); // 直接修改默認的 child.prototype Object.setPrototypeOf(child.prototype, parent.prototype);
經過對比發現:如果忽略Object.create() 帶來的輕微的損失(拋棄的對象需要進行垃圾回收),它比 ES6 的方法有更好的可讀性。
四、拷貝繼承
也是 jQuery 實現繼承的方法
// 拷貝繼承 function extend() { var options, src, copy, copyIsArray, clone, target = arguments[0] || {}, deep = false, i = 1; if ( typeof target === ‘boolean‘) { deep = target; target = arguments[i] || {}; i++; } if ( typeof target !== ‘object‘ && !isFun(target)) { target = {}; } // 循環一個或多個要拷貝的對象 for( ; i<arguments.length; i++ ) { if ( (options = arguments[i]) != null ) { for ( name in options ) { src = target[name]; copy = options[name]; // 防止死循環 if ( target === copy ) { continue; } copyIsArray = isArray(copy); if ( deep && copy && ( isPlainObject(copy) || copyIsArray ) ) { // 深拷貝 if ( copyIsArray ) { copyIsArray = false; clone = src && isArray(src) ? src : []; } else { clone = src && isPlainObject(src) ? src : {}; } target[name] = extend( deep, clone, copy ); // 防止拷貝 undefined } else if ( copy !== undefined ) { target[name] = copy; } } } } return target; } function isFun(obj) { return type(obj) === ‘[object Function]‘; } function isPlainObject(obj) { return type(obj) === ‘[object Object]‘; } function isArray(obj) { // IE8不支持 if (Array.isArray) { return Array.isArray(obj); } else { return type(obj) === ‘[object Array]‘; } } function type(obj) { if ( obj == null ) { // obj + ‘‘ = ‘null‘/‘undefined‘ return false; } return Object.prototype.toString.call(obj); } var object1 = { apple: 0, banana: { weight: 52, price: 100 } }; var object2 = { banana: { price: 200 }, cherry: 97 }; extend(true, object1, object2);
試一試
破解 JS(原型)繼承