JavaScript原型(下)

97 阅读2分钟

Object.create()

我们常见的创建对象的方式有两种,一种是通过new Object(),一种是通过对象字面量的形式。而这里我们还会再介绍的另外一种方式 —— Object.create()

Object.create(null)

let empty_obj = {};
let better_empty_obj = Object.create(null);

console.log(empty_obj);   // {}  有__proto__属性
console.log(better_empty_obj);   // {}  无__proto__属性

通过 Object.create(null) 创建的对象没有 Object.prototype 这个委托,所以比 {} 更空

创建关联,制造委托(对象与对象之间)

Object.create()是一个大英雄,并不只是用来让我们生成一个更空的对象(哈哈)

let foo = {
  tell() {
    console.log('tell me something');
  }
};

let bar = Object.create(foo);

bar.tell();  // tell me something

创建一个空对象bar,并将这个对象的[[Prototype]]属性关联到foo对象,这样做和通过new一个构造函数都可以创建一个新对象,并使其[[Prototype]]属性关联到另一个对象,但是通过Object.create()的方式不会让新对象存在.constructor这样的引用(具体参看:关于constructor属性

更多参数

let foo = {
  a: 'aaa'
};

let bar = Object.create(foo, {
  b: {
    enumerable: false,
    writable: true,
    configurable: true,
    value: 'bbb'
  },
  c: {
    enumerable: true,
    writable: false,
    configurable: true,
    value: 'bbb'
  }
});

bar.hasOwnProperty('a')  // false
bar.hasOwnProperty('b')  // true
bar.hasOwnProperty('c')  // true

bar.a // aaa  
bar.b // bbb
bar.c // ccc

Object.create()的第二个参数可以为新对象增加新的属性(并可以通过属性描述符配置)

“类”与“委托”的对比

这里的类并不是说JS中有类的概念,只是模拟出这个感觉

第一段“类”代码

function Foo(who) {
  this.me = who;
}
Foo.prototype.identify = function() {
  return "I am " + this.me;
}

function Bar(who) {
  Foo.call(this, who);
}
// 创建委托关系
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.speak = function() {
  console.log('Hello, ' + this.identify());
}

var b1 = new Bar("b1");
var b2 = new Bar("b2");

b1.speak();  // Hello, I am b1
b2.speak();  // Hello, I am b2

console.log(b1.constructor);
console.log(b2.constructor);  
// ƒ Foo(who) {
//   this.me = who;
// }

console.log(Bar.prototype.constructor === b1.constructor);  // true
image-20210622150857613

第二段“委托”代码

let Foo = {
  init: function(who) {
    this.me = who;
  },
  identify: function () { 
    return "I am " + this.me;
  }
};

let Bar = Object.create(Foo);
Bar.speak = function() {
  console.log("Hello, " + this.identify());
}

var b1 = Object.create(Bar);
var b2 = Object.create(Bar);
b1.init('b1');
b2.init('b2');

b1.speak();  // Hello, I am b1
b2.speak();  // Hello, I am b2
image-20210622150946646

通过比较我们可以清晰的看出关联风格(“委托”)更加的简洁,因为代码只关注一件事,对象之间的关联

关于constructor属性

对于==新==改动[[Prototype]]属性的原型对象来说,其constructor属性才会依据委托关系而改动!

先来分析一段代码

function Person() {

}

Person.prototype = {
  name: 'new obj'
};

let per = new Person();

console.log(per.constructor === Person);  // ?
console.log(per.constructor === Object);  // ?

这里的打印的值应该是什么?

我们先来看看下面这段代码

function Person() {

}

let per = new Person();

console.log(per.constructor === Person);  // true

通过图解: image-20210615185822278

首先我们要明确的是,perconstructor属性是委托于(来自于)Person.Prototype对象

所以最上面的代码我们可以拆解为:

function Person() {

}

Person.prototype = new Object();
Person.prototype.name = 'new obj';

let per = new Person();

console.log(per.constructor === Person);  // false
console.log(per.constructor === Object);  // true
console.log(Person.prototype.constructor === Object);  // true

通过图解: image-20210615190330317

所以per对象在获取constructor属性时,会沿着原型链一直往上寻找委托的constructor属性,直到此图中的Object.Prototype对象,所以打印结果如上面的代码

所以第一段代码中结果为:

console.log(per.constructor === Person);  // false
console.log(per.constructor === Object);  // true