破解 JS(原型)继承

Posted 烟火

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了破解 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(原型)继承的主要内容,如果未能解决你的问题,请参考以下文章

js组合继承(原型继承+借用构造函数继承)

js 继承与原型链

js中的继承

js继承问题

JS 类继承和原型继承区别

js继承之组合继承(结合原型链继承 和 借用构造函数继承)