# 原型链与对象

# 创建对象的 3 种方法

  1. 字面量方式
var o1 = { name: "o1" };
var o11 = new Object({ name: "o11" });
  1. 使用“构造函数”
function M(name) {
	this.name = name;
}

var o2 = new M("o2");
  1. 使用 Object.create
var p = { name: "p" };

var o3 = Object.create(p);
手写实现 Object.create
Object.create =
	Object.create ||
	function(obj) {
		var F = function() {};
		F.prototype = obj;

		return new F();
	};

控制台输出:

  • o1、o11、o3 都是 Object {},o2 是构造函数 M {};
  • o3 相比于 o1 和 o11 又特殊在:Object.create 所创建的是一个空对象,传入的 p 是此空对象实例的原型对象,即 o3.proto指向 p; 所以 o3 本身是不具备 name 属性的,需要访问它对应的原型对象 p 上的 name 属性。

# 构造函数、实例、原型对象 间的关系

关系图:构造函数、实例、原型对象

WARNING

prototype(显式原型属性) __proto__(隐式原型属性)

# 解释

  • M 是构造函数
  • o2 是 M 的实例
  • M.prototype 是 M 的原型对象
  • 原型对象中有个 constructor 属性指向 M
  • 实例 o2 中有个proto属性指向原型对象 M.prototype
  • 注意:对象(不管是实例对象还是原型对象)都有__proto__属性,但只有构造函数才有prototype属性
  • 注意:构造函数其实也是对象,是Function构造函数的实例对象,所以构造函数也有__proto__属性指向Function.prototypejs M.__proto__ === Function.prototype // true // 构造函数M是Function这个构造函数的一个实例。
    例题
Function.prototype.a = () => alert(1);
Object.prototype.b = () => alert(2);
function A() {}

const a = new A();
a.a(); // 报错
a.b();
  • a 是 A 构造的实例对象,A.prototype === a.__proto__,而A.prototype.__proto__ === Object,所以 a.b()能得到() => alert(2);
  • 但 a.a()是取不到的,会报错,并阻碍之后的输出,也就是说 a 根本不继承 Function 对象。
  • A.__proto__ === Function.prototype,因此 A.a() 能取到。 :::
类似的题
var Person = function() {};

Object.prototype.a = "A";

Function.prototype.b = "B";

var p = new Person();
console.log(p.a); //A
console.log(p.b); //undefined
  • Person 函数才是 Function 对象的一个实例,所以通过 Person.a 可以访问到 Function 原型里面的属性,
  • 但是 new Person()返回来的是一个对象,它是 Object 的一个实例,是没有继承 Function 的,所以无法访问 Function 原型里面的属性。
  • 但是,由于在 js 里面所有对象都是 Object 的实例,所以,Person 函数可以访问到 Object 原型里面的属性,Person.b => 'b'。

# 什么是原型链

# 定义

实例通过proto属性指向它对应的原型对象,此原型对象当中也有proto属性指向其对应的原型对象,这样一层层向上形成链式结构,直至Object.prototype原型对象便到达原型链的顶端

# 原型链的工作原理

原型对象上的方法是被不同的实例所共有的,这就是原型链的作用。 也就是说,当一个实例在访问一个属性的时候,先在自身找是否存在此属性,如果没有就向上一级查询自己的原型对象,如果没有就继续向上一级查询原型对象,直到找到此属性或者到达原型链的顶端,这就是原型链的工作原理

# new 运算符工作原理

# new 的工作步骤

  • 步骤一:创建新实例对象 o,关联构造函数 M 的原型对象;
  • 步骤二:执行构造函数 M,并绑定 M 的作用域上下文到新实例对象 o 上;
  • 步骤三:判断步骤二中返回的 res 是否为(广义上)对象,如果是对象则抛弃 o 返回 res;如果不是对象则返回 o。

# 模拟实现 new 操作符

var thisNew = function(M) {
	// Object.create()返回空对象o,并关联M的原型对象
	var o = Object.create(M.prototype);
	// 执行构造函数M,并绑定作用域到o上
	var res = M.call(o);
	// 判断res是否是广义上的对象,是则返回res,否则返回o
	if (res instanceof Object) {
		// 使用typeof不行,因为需要排除null的情况
		return res;
	} else {
		return o;
	}
};

验证模拟的代码是否能完成 new 运算符的功能:

验证模拟new操作符

即使是再在 M 的原型对象上追加 run 方法,通过我们模拟的 thisNew 所创建的实例 o 也具备 run 方法。从而证明了 new 运算符背后的工作原理。

Last Updated: 4/21/2021, 5:50:33 PM