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