今天是国庆节的第8天,尽管假期结束,但也不打破自己的学习计划,今天的主题是:总结Object.create()
总结此文的原因是:碰到继承的时候经常用到它,引发我去深入了解其用法。
此文能收货:
- new Object() 和 Object.create()的区别
- 操作原型对象(prototype)的方法
- Object.create()
- Object.setPrototypeOf
- Object.getPrototypeOf()
- 不同属性对应不同的继承方法
- 如果只是拷贝自身可枚举属性
- 如果要拷贝原型上的属性
- 如果要拷贝get /set 属性
需要了解详情,请看下文。
一、Object.create()
-
描述:该方法创建一个新对象,使用现有的对象来提供新创建的对象的
__proto__ -
语法:Object.create(proto, [propertiesObject])
- proto:必须。表示新建对象的原型对象,即该参数会被赋值到目标对象(即新对象,或说是最后返回的对象)的原型上。该参数可以是null、对象、函数的prototype属性 (
注:创建空的对象时需传null , 否则会抛出TypeError异常)。 - propertiesObjec:可选。 添加到新创建对象的可枚举属性(即其自身的属性,而不是原型链上的枚举属性)对象的属性描述符以及相应的属性名称。
这些属性对应Object.defineProperties()的第二个参数。
- proto:必须。表示新建对象的原型对象,即该参数会被赋值到目标对象(即新对象,或说是最后返回的对象)的原型上。该参数可以是null、对象、函数的prototype属性 (
-
返回值:在指定原型对象上添加新属性后的对象
二、new Object()
var objB = new Object();
// var objB = Object();
objB.name = 'b';
objB.sayName = function() {
console.log(`My name is ${this.name} !`);
}
objB.sayName();
console.log(objB.__proto__ === Object.prototype); // true
console.log(objB instanceof Object); // true
new操作符其实做了以下四步:
function F() {};
var func = new F();
var obj = new Object(); // 创建一个空对象
obj.__proto__ = F.prototype; // obj的__proto__指向构造函数的prototype
var result = F.call(obj); // 把构造函数的this指向obj,并执行构造函数把结果赋值给result
if (typeof(result) === 'object') {
func = result; // 构造函数F的执行结果是引用类型,就把这个引用类型的对象返回给objB
} else {
func = obj; // 构造函数F的执行结果是值类型,就返回obj这个空对象给objB
}
三、new Object() 和 Object.create()的区别
1. 创建对象的方式不同
new Object() 方式:通过构造函数来创建对象, 添加的属性是在自身实例下
let a = { fruit : 'apple' }
let b = new Object(a)
console.log(b) // {fruit: "apple"}
console.log(b.__proto__) // {}
console.log(b.fruit) // apple
Object.create() 方式:继承一个对象, 添加的属性是在原型下
let a = { fruit: 'apple' }
let b = Object.create(a)
console.log(b) // {}
console.log(b.__proto__) // {fruit: "apple"}
console.log(b.fruit) // apple
2. 创建对象属性描述符不同
Object.create用第二个参数创建非空对象的属性描述符默认是为false的,不可写,不可枚举,不可配置
let obj = Object.create({}, { age: { value: 18 } })
console.log(Object.getOwnPropertyDescriptors(obj))
>
{
age: {
value: 18,
writable: false,
enumerable: false,
configurable: false
}
}
obj.age = 24
obj.age
> 18
obj.q = 12
for (var prop in obj) {
console.log(prop)
}
> "q"
delete obj.age
> false
字面量方法创建的对象属性的描述符默认为true
let obj = Object.create(null)
obj.age = { value: 18 }
console.log(Object.getOwnPropertyDescriptors(obj))
{
age: {
value: { value: 18 },
writable: true,
enumerable: true,
configurable: true
}
}
构造函数创建的对象属性的描述符默认为true
let age = { value: 18 }
console.log(Object.getOwnPropertyDescriptors(age))
{
value: {
value: 18,
writable: true,
enumerable: true,
configurable: true
}
}
3. 创建空对象时,是否有原型属性不同
当用构造函数或对象字面量方法创建空对象时,对象时有原型属性的,即有_proto_
console.dir(new Object()) //{}
>
Object
__proto__:
当用Object.create()方法创建空对象时,对象是没有原型属性的
console.dir(Object.create(null))
>
Object
No properties
四、操作原型对象(prototype)的方法
Object.create()
描述:该方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
格式:Object.create(proto, [propertiesObject])
用法:如果用传统的方法要给一个对象的原型上添加属性和方法,是通过prototype 实现的
let proto = {
age: 18,
name: 'hannie',
show(){}
};
let obj = Object.create(proto);
console.dir(obj)
>
Object
__proto__:
age: 18
name: "hannie"
show: ƒ show()
__proto__: Object
var a = Object.create(null);
console.dir(a); // {}
console.log(a.__proto__); // undefined
console.log(a.__proto__ === Object.prototype); // false
console.log(a instanceof Object); // false 没有继承`Object.prototype`上的任何属性和方法,所以原型链上不会出现Object
如果是不用Object.create()方法,我们是通过构造函数或者类给对象原型添加属性和方法的?
let Person = function(){}
Person.prototype.age = 18
Person.prototype.name = "hannie"
Person.prototype.show = function() {}
//通过构造函数创建实例
var p = new Person();
console.log(p.__proto__ === Person.prototype) // true
console.dir(p)
>
Person
__proto__:
age: 18
name: "hannie"
show: ƒ show()
__proto__: Object
console.dir(p.__proto__)
>
Object
age: 18
name: "hannie"
show: ƒ show()
__proto__: Object
Object.setPrototypeOf
描述:该方法的作用与 __proto__ 相同,用来设置一个对象的 prototype 对象,返回参数对象本身。 ES6 正式推荐的设置原型对象的方法。
格式:Object.setPrototypeOf(object, prototype)
let person = {
age: 18,
name: "hannie"
};
let o = { sex: '女' };
Object.setPrototypeOf(o, person);
console.dir(o)
>
Object
sex: "女"
__proto__:
age: 18
name: "hannie"
__proto__: Object
输出结果中看出,添加的方法是在原型上的。就类似于
obj.__proto__ = proto;
Object.getPrototypeOf()
描述:用于读取一个对象的原型对象;
格式:Object.getPrototypeOf(obj);
let person = {
age: 18,
name: "hannie"
};
let o = { sex: '女' };
Object.setPrototypeOf(o, person);
console.log(Object.getPrototypeOf(o))
>
{
age: 18,
name: "hannie",
__proto__: Object //其实这个除了Object.create()方法创建的空对象,其他每个对象都会有
}
Object.getPrototypeOf('foo') === String.prototype
>true
Object.getPrototypeOf(true) === Boolean.prototype
>true
以上方法引出原型属性的继承
五、原型属性的拷贝(继承)
在原型上定义方法
function Person(age){
this.age = age
}
Person.prototype.name = 'hannie'
Person.prototype.call = function(phone){
console.log('The phone is:',phone)
}
let like = {a: 1, b: 2, c: 3};
Object.assign(Person.prototype,like)
let person = new Person(18)
console.log(person);
>
Person
age: 18
__proto__:
a: 1
b: 2
c: 3
call: ƒ (phone)
name: "hannie"
constructor: ƒ Person(age)
__proto__: Object
拷贝实例上的方法:Object.assign
let person1 = Object.assign({},person)
console.log(person1.age); // 能拷贝到实例上的方法
>18
console.log(person1.a); // 不能拷贝到原型上的方法
>undefined
怎么才能拷贝原型上的方法呢?
方法一:Object.getPrototypeOf + Object.create + Object.assign
let originProto = Object.getPrototypeOf(person)
let originProto2 = Object.create(originProto);
let person1 = Object.assign(originProto2, person)
console.log(person1.age);
>18
console.log(person1.a); //可以拷贝原型上的方法
>1
方法二(推荐):Object.getPrototypeOf + Object.getOwnPropertyDescriptors + Object.create
Object.create()的参数理解为:第一个参数是放在新对象的原型上的,第二个参数是放在新对象的实例上的。
推荐原因:因为Object.assign() 方法不能正确拷贝 get ,set 属性
let originProto = Object.getPrototypeOf(person)
let descriptor = Object.getOwnPropertyDescriptors(person)
let person1 = Object.create(originProto, descriptor);
console.log(person1.age);
>18
console.log(person1.a); //可以拷贝原型上的方法
>1
下面对比两种方法,哪方法种能正确拷贝 get ,set 属性
Object.defineProperty(person,'ageGet', {
enumerable: true, // 设为可枚举,不然 Object.assign 方法会过滤该属性
get(){
return "Could get: " + this.age
}
});
let originProto = Object.getPrototypeOf(person)
let descriptor = Object.getOwnPropertyDescriptors(person)
let person1 = Object.create(originProto, descriptor);
console.log(person1); //18
>
Person
age: 18
ageGet: "Could it return 18"
get ageGet: ƒ get()
__proto__: Object
Object.defineProperty(person,'ageGet', {
enumerable: true, // 设为可枚举,不然 Object.assign 方法会过滤该属性
get(){
return "Could get: " + this.age
}
});
let originProto = Object.getPrototypeOf(person)
let originProto2 = Object.create(originProto);
let person1 = Object.assign(originProto2, person)
>
Person
age: 18
ageGet: "Could get: 18"
__proto__: Object
结果中没有get ageGet: ƒ get()
得出结论:
方法一不能正确拷贝 get ,set 属性;
方法二能正确拷贝 get ,set 属性
虽然说实际开发上很少要去修改 get 描述符,但是多知道一种方法,遇到情况时就知道怎么去解决了。
再实例化新对象,发现没ageGet,证明get也不是原型上的方法
let person2 = new Person(18)
console.log(person2);
>
Person
age: 18
__proto__: Object
综合以上,原型属性的继承可以有以下几种方法:
let originProto = Object.getPrototypeOf(person)
//方法1
const obj = Object.create(originProto);
obj.other = 123;
//方法2
const obj = Object.assign(
Object.create(originProto),
{
other: 123,
}
);
/方法3
const obj = Object.create(originProto,Object.getOwnPropertyDescriptors({ other: 123 }));
总结
new Object() 和 Object.create()的区别:
- 创建对象的方式不同
- 创建对象属性描述符不同
- 创建空对象时,是否有原型属性不同
操作原型对象(prototype)的方法:
- Object.create()
- Object.setPrototypeOf
- Object.getPrototypeOf()
属性的拷贝(继承):
- 如果只是拷贝自身可枚举属性,就可以只用 Object.assign 方法;
- 如果要拷贝原型上的属性,使用 Object.assign + Object.create + Object.getPrototypeOf 或者 Object.getPrototypeOf + Object.getOwnPropertyDescriptors + Object.create
- 如果要拷贝get /set 属性,使用Object.getPrototypeOf + Object.getOwnPropertyDescriptors + Object.create