Object.create()和JS继承

Object.create()介绍

语法:

Object.create(proto [, propertiesObject])

参数:

  • proto:必须。表示新建对象的原型对象,即该参数会被赋值到目标对象(即新对象,或说是最后返回的对象)的原型上。该参数可以是null(创建空的对象时需传null), 对象, 函数的prototype属性 , 否则会抛出TypeError异常。
  • propertiesObject:可选。该参数对象是一组属性与值,该对象的属性名称将是新创建的对象的属性名称,值是属性描述符(这些属性描述符的结构与 Object.defineProperties() 的第二个参数一样)。这些属性是新对象自身的属性,而不是新对象原型链上的属性。

返回值:

在指定原型对象上添加新属性后的对象。


声明对象的三种方式

看下面一段代码:

var test1 = {x: 1};  // {x:1,__proto__:Object}
var test2 = new Object(test1); //{x:1,__proto__:Object}

console.log(test1.__proto__ === test2.__proto__); //true

var test3 = Object.create(test1); //{__proto__:{x:1,__proto__:Object}}
var test4 = Object.create(null); //{}
//test4是空对象,没有继承原型属性和方法,console.log(test4.__proto__)输出undefined

var test5 = function() {}
test5.prototype = test1;
var test6 = new test5(); //{__proto__:{x:1,__proto__:Object}}

// test3 等价于 test6,它们的原型链(__proto__)都是指向test1
console.log(test6.__proto__ === test3.__proto__); //true


注意:Object.create() 用第二个参数来创建对象属性的属性描述符默认是为false的,而构造函数或字面量方法创建的对象属性的描述符默认为true。看下面一段代码:

var o = Object.create(Object.prototype, { p: { value: 42 } })

// 省略了的属性特性默认为false,所以属性p是不可写,不可枚举,不可配置的
o.p = 24
console.log(o.p) //42

o.q = 12
for (var prop in o) {
    console.log(prop)
}
//q

delete o.p //false

 将属性描述符打印出来,看下图:


继承

JavaScript 的对象继承是通过 原型链 实现的。

ES6 提供了更多原型对象的操作方法。__proto__ 属性(前后各两个下划线),用来读取或设置当前对象的prototype对象。目前只有浏览器环境必须部署有这个属性,其他运行环境不一定要部署,因此不建议使用这个属性,而是使用下面这些 Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)来代替。

  • Object.setPrototypeOf()
    • 格式:Object.setPrototypeOf( obj, proto )
    • 描述:它是ES6正式推荐的设置原型对象的方法,相当于 obj.__proto__ = proto
    • 返回:第一个参数obj本身
  • Object.getPrototypeOf()
Object.getPrototypeOf('foo') === String.prototype // true
Object.getPrototypeOf(true) === Boolean.prototype // true

注意:Object.assign() 方法不能正确拷贝 get ,set 属性。

function ColoredTriangle() {
  this.color = 'red';
}

var c = new ColoredTriangle();
Object.defineProperty(c,'colorGet', {
    enumerable: true, // 设为可枚举,不然 Object.assign 方法会过滤该属性
    get(){
        return "Could it return " + this.color
    }
});

var c3 = Object.assign(Object.create(Object.getPrototypeOf(c)), c)


c3.__proto__ === ColoredTriangle.prototype //true
c3 instanceof ColoredTriangle //true

 结果如下:

这里没有拷贝到 colorGet 的 get 描述符,而是直接把获取到的值赋值给 colorGet。

那对于 get 描述符要怎么获取呢? Object.getOwnPropertyDescriptors() 就专为解决这问题而生。

如下:

function ColoredTriangle() {
  this.color = 'red';
}

var c = new ColoredTriangle();
Object.defineProperty(c,'colorGet', {
    enumerable: true, // 设为可枚举,不然 Object.assign 方法会过滤该属性
    get(){
        return "Could it return " + this.color
    }
});

var c3 = Object.create(Object.getPrototypeOf(c), Object.getOwnPropertyDescriptors(c));

结果如下:

 

上面实现 继承 的两种方法中用到了 Object.getOwnPropertyDescriptors Object.assing() 、Object.create()Object.getPrototypeOf()方法,通常这几种方法都有一起结合使用。

 

如果只是拷贝 自身可枚举属性,就可以只用 Object.assign 方法

如果是要拷贝原型上的属性,就需要 Object.assign , Object.create, Object.getPrototypeOf 方法结合使用

如果是拷贝get /set 属性,就需要 结合 Ojbect.getOwnPropertyDescriptors 方法

 


JS原型

  • 实例上的__proto__指向原型上的prototype,prototype和__proto__的区别:实例.__proto__ === 原型.prototype
  • 凡是通过new Function()创建的对象都是函数对象,其他的都是普通对象
  • 每个对象都有__proto__属性,但只有函数对象才有 prototype 属性,这都是js解释器自动加上的
  • 所有函数对象的__proto__都指向 Function.prototype
  • 每一个函数新建的时候都有一个默认的prototype,prototype这个对象(原型对象)上面默认有一个指向自己的constructor
  • 原型对象主要作用是用于继承
  • 原型对象其实就是普通对象(但 Function.prototype 除外,它是函数对象,它很特殊,它是一个空函数(Empty function),它没有prototype属性(前面说过函数对象都有prototype属性))
  • Function.prototype是唯一一个typeof XXX.prototype为 function的prototype。其它构造器的prototype都是一个对象
  • typeof Object/Function/Array/Date/Number/String/Boolean,得到的结果都是"function",所以它们的__proto__都指向Function.prototype
  • 所有的构造器都来自于 Function.prototype,甚至包括根构造器Object及Function自身。所以所有构造器都继承了Function.prototype的属性及方法。如length、call、apply、bind
  • Object.prototype的proto指向null,所以null是原型链的终端。
    typeof Object  //"function"
    Object.constructor === Function  //true
    Object.__proto__ === Function.prototype //true
    //Function,Array,String,Date,Number,Boolean,RegExp,Error同以上,它们都是构造器

    //Math,JSON是以对象形式存在的,无需new,它们的proto是Object.prototype
    Math.__proto__ === Object.prototype  // true
    Math.constructor === Object // true
    JSON.__proto__ === Object.prototype  // true
    JSON.constructor === Object //true

    typeof Function.prototype  //"function"
    typeof Object.prototype //"object"
    //除Function外的构造器的prototype用typeof得到的结果都是"object"

    typeof Function.prototype.prototype //"undefined"
    typeof Object.prototype.prototype //"undefined"
    Function.prototype.__proto__ === Object.prototype //true
    Object.prototype.__proto__ === null //true

 


ES6之前JS继承的几种方式

    // 先定义一个父类
    function Animal (name) {
        // 实例属性
        this.name = name || 'Animal';
        // 实例方法
        this.sleep = function () {
            console.log(this.name + '正在睡觉!');
        }
    }
    // 原型方法
    Animal.prototype.eat = function (food) {
        console.log(this.name + '正在吃:' + food);
    }
  • 原型链继承
    • 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
    • 简单,易于实现
    • 父类新增的原型方法/原型属性,子类都能访问到
    • 无法实现多继承
    • 来自原型对象的所有属性被所有实例共享
    • 创建子类实例时,无法向父类构造函数传参
    • 要想为子类新增原型属性和方法,则必须放在new Animal()这样的语句之后执行
    function Cat(){}
    Cat.prototype = new Animal();
    Cat.prototype.name = 'Tom';

    var cat = new Cat();
    cat.name; // Tom
    cat.eat('fish'); // Tom正在吃:fish
    cat.sleep(); // Tom正在睡觉!
    cat instanceof Animal; // true
    cat instanceof Cat; // true
  • 构造继承(call()/apply())

    • 可以实现多继承
    • 解决了1中子类实例共享父类属性的问题
    • 创建子类实例时,可以向父类传递参数
    • 实例并不是父类的实例,只是子类的实例
    • 只能继承父类的实例属性和方法,不能继承原型属性/方法
    • 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
    function Cat (name) {
        Animal.call(this);
        this.name = name || 'Tom'
    }

    var cat = new Cat();
    cat.name; // Tom
    cat.sleep(); // Tom正在睡觉!
    cat.eat('fish'); // 报错:Uncaught TypeError: cat.eat is not a function
    cat instanceof Animal; // false
    cat instanceof Cat; // true
  • 实例继承

    • 声明一个父类实例,为父类实例添加新特性,作为子类实例返回
    • 不限制调用方式,不管是new 子类()还是子类(),返回的对象具有相同的效果
    • 实例是父类的实例,不是子类的实例
    • 不支持多继承
    function Cat (name) {
        var instan  = new Animal();
        instan.name = name || 'Tom';
        return instan;
    }

    var cat = new Cat();
    cat.name; // Tom
    cat.sleep(); // Tom正在睡觉!
    cat.eat('fish'); // Tom正在吃:fish
    cat instanceof Animal; // true
    cat instanceof Cat; // false
  • 拷贝继承

    • 支持多继承
    • 效率较低,内存占用高
    • 无法获取父类不可枚举的方法(不能使用for in访问到的方法)
    function Cat (name) {
        var animal = new Animal();
        for (var p in animal) {
            Cat.prototype[p] = animal[p];
        }
        Cat.prototype.name = name || 'Tom';
    }

    var cat = new Cat();
    cat.name; // Tom
    cat.sleep(); // Tom正在睡觉!
    cat.eat('fish'); // Tom正在吃:fish
    cat instanceof Animal; // false
    cat instanceof Cat; // true
  • 组合继承(prototype、call()/apply())

    • 弥补了 构造继承 中的缺陷,可以继承实例属性和方法,也可以继承原型属性和方法
    • 可传参
    • 有双份实例属性和方法,略耗内存
    function Cat (name) {
        Animal.call(this)
        this.name = name || 'Tom'
    }
    Cat.prototype = new Animal();

    var cat = new Cat();
    cat.name; // Tom
    cat.sleep(); // Tom正在睡觉!
    cat.eat('fish'); // Tom正在吃:fish
    cat instanceof Animal; // true
    cat instanceof Cat; // true
  • 寄生组合继承

通过寄生方式,砍掉父类的实例属性,弥补了组合继承中的缺陷

    //假设Animal是父类
    function Cat(name) {
        Animal.call(this)
        this.name = name || 'Tom'
    }
    (function(){
        //创建一个没有实例属性和方法的类
        var Super = function(){}
        Super.prototype = Animal.prototype
        Cat.prototype = new Super()
    })();

    var cat = new Cat();
    cat.name; // Tom
    cat.sleep(); // Tom正在睡觉!
    cat.eat('fish'); // Tom正在吃:fish
    cat instanceof Animal; // true
    cat instanceof Cat; // true

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值