原型链
总结:用于在现有对象的基础上构建新类型的对象。类似于类的语言中的继承。
对象实例上的原型可通过 Object.getPrototypeOf(object) 或 proto 属性获得,而构造函数上的原型可以通过 Object.prototype 获得。
虽然 Object.prototype 是原型链的最顶层,但它本身也有一个原型对象 => null
。所以原型链的顶端实际上是 null 。总的来说,JavaScript 中的原型链顶端是 null,而原型链的最顶层对象是 Object.prototype。
构造函数创建对象
function Fruit() {}
var fruit = new Fruit();
fruit.name = 'Apple'
console.log(fruit.name) // Apple
Fruit 就是构造函数,fruit 是实例对象。对象通过 new 创建。
prototype
先看一段代码
function Fruit() {}
Fruit.prototype.name = 'Apple';
const fruit1 = new Fruit();
const fruit2 = new Fruit();
console.log(fruit1.name) // Apple
console.log(fruit2.name) // Apple
每个函数都有一个 prototype 属性,函数的 prototype 属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型,也就是这个例子中的 fruit1 的原型。
简单理解原型:每一个 JavaScript 对象 ( null 除外) 在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。
我们已经知道,函数通过 prototype 和原型关联,那么实例对象是如何与原型关联的呢。
__proto__
function Fruit(){}
var fruit = new Fruit();
console.log(fruit.__proto__ === Fruit.prototype); // true
从例子中可以发现 对象 是通过 __proto__
与原型关联。虽然大部分浏览器都支持通过 __proto__
访问原型,但是它并不存在于 Fruit.prototype 中。本质上是 Object.prototype 的 getter/setter。
constructor
每个原型都有一个 constructor 属性指向关联的构造函数。
function Fruit(){}
console.log(Fruit === Fruit.prototype.constructor); // true
实例与原型
读取实例属性的过程,实际是原型查找的过程。通过查找与对象关联的原型中的属性(如果查不到会继续往查找原型的原型,直到查到顶层 null )。
function Fruit(){}
Fruit.prototype.name = 'Apple';
const fruit = new Fruit();
console.log(fruit.name); //1 Apple
fruit.name = 'Banana';
console.log(fruit.name); //2 Banana
delete fruit.name;
console.log(fruit.name); //3 Apple
在 1 中给实例原型添加了 name = Apple 的属性,所以在实例化之后,fruit 中找不到 name 属性,就会从原型中 fruit.__proto__
也就是 Fruit.prototype
中查找。3 同理,删除对象中的 name 属性后会在原型查找。而在 2 中,由于给对象赋予了 name 属性,所以不需要查找原型即可得到 name。
原型链
其中被 __proto__
串起来的链状结构就是原型链。而原型链的顶层为 null(Object.prototype.__proto__ == null)。
从上面的例子来看,原型链大致呈:Fruit() --> fruit --__proto__
--> Fruit.prototype --__proto__
--> Object.prototype --__proto__
--> null
new 是如何创建实例
简单来讲,new 表达式是配合构造函数,通过 new 一个构造函数取继承属性。
- 创建一个新的空对象。
- 当使用 new 操作符时,JavaScript 首先创建一个新的空对象。这个对象将成为即将创建的实例。
- 将新对象的
__proto__
属性设置为构造函数的prototype
属性。- 对象的内部 [[Prototype]](即
__proto__
)属性被设置为构造函数的prototype
属性。这一步确保了通过 new 创建的对象能够访问构造函数原型链上的属性和方法。
- 对象的内部 [[Prototype]](即
- 执行构造函数并传入新对象作为 this。
- 将新创建的对象作为 this 上下文传递给构造函数。构造函数中的代码通常会为新对象添加属性和方法。
- 返回新对象。
- 如果构造函数显式地返回了一个对象,则 new 表达式的结果将是该对象;否则,new 将返回创建的对象实例。
通过原型实现 new 操作
function Fruit(name) {
this.name = name;
}
function createInstance(Constructor, ...args) {
// 1&2. 创建空对象,并且 __proto__ 设置为 prototype
const obj = Object.create(Constructor.prototype);
// 3. 改变 this 指向
const result = Constructor.apply(obj, args);
// 4. 返回
return result instanceof Object ? result: obj;
}
const fruit = createInstance(Fruit, "Apple");
console.log(fruit.name); // Apple