1. 程式人生 > >破解 JS(原型)繼承

破解 JS(原型)繼承

ocs deep zh-cn 原型對象 != 添加屬性 false 操作 arr

總體分為四大類:利用空對象作為中介繼承、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(原型)繼承