__protot__和prototype的关系
在 JavaScript 中,__proto__ 和 prototype 都与原型系统相关,但它们的作用和存在位置不同。让我用清晰的示例来解释它们的关系。
核心区别
| 特性 | prototype | __proto__ |
|---|---|---|
| 所有者 | 只有函数对象才有 | 所有对象都有 |
| 作用 | 构造函数创建实例时使用的原型模板 | 对象实际指向的原型 |
| 访问方式 | Func.prototype | obj.__proto__ 或 Object.getPrototypeOf(obj) |
原型(显示原型)
- 函数天生拥有一个属性
prototype,新创建对象的隐式原型(__proto__)会指向该函数的prototype对象 - 意义: 将构造函数中的一个固定属性和方法挂载到原型上,在创建实例的时候,不需要重复执行这些属性和方法
- 挂载在原型上的属性和方法,在实例对象中可以直接访问到
- 实例对象无法修改和删除原型上的属性和方法
function Person(name) {
this.name = name;
}
// 将方法添加到原型上
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name}`);
};
const person1 = new Person('Alice');
const person2 = new Person('Bob');
person1.sayHello(); // Hello, my name is Alice
person2.sayHello(); // Hello, my name is Bob
console.log(person1.sayHello === person2.sayHello); // true - 共享同一个方法
对象原型(隐式原型)
- 正常每一个对象都拥有一个属性
__proto__,该属性值是一个对象,这个对象就是当前实例的原型 - v8在访问对象中的属性时,会先访问对象上显示拥有的属性,如果找不到,就会去对象的隐式原型中查找,也就是
__proto__ - 实例对象的隐式原型===构造函数的显示原型,这是new原理导致的
- 意义:让实例对象继承到构造函数原型上的属性和方法,方便我们为某一个数据类型添加属性和方法
new构造函数的原理
- 创建一个空对象
- 让构造函数的this指向这个空对象
- 执行构造函数中的代码
- 将这个空对象的隐式原型
__proto__赋值成构造函数的显示原型 - 返回这个对象
function Person(name, age) {
// new 执行时,this 已经指向新创建的对象
this.name = name;
this.age = age;
// 方法最好不要定义在构造函数内(每次new都会创建新函数)
this.sayName = function() {
console.log(this.name);
};
}
// 推荐将方法定义在原型上(所有实例共享)
Person.prototype.sayAge = function() {
console.log(this.age);
};
// 使用 new 创建实例
const person1 = new Person("Alice", 25);
逐步解析 new Person("Alice", 25) 的执行过程
-
创建一个空对象
javascript
const obj = {}; -
将构造函数的
this指向这个空对象// 内部相当于执行了:Person.call(obj, "Alice", 25); // this 现在指向 obj -
执行构造函数中的代码
obj.name = "Alice"; obj.age = 25; obj.sayName = function() { console.log(this.name); }; -
设置对象的原型链
obj.__proto__ = Person.prototype; // 现代浏览器推荐使用: // Object.setPrototypeOf(obj, Person.prototype); -
返回这个对象
return obj; ### 验证原型链
验证原型链
console.log(person1.__proto__ === Person.prototype); // true
console.log(Person.prototype.constructor === Person); // true
person1.sayAge(); // 25 (从原型继承的方法)
原型链
v8在访问对象中的属性时,会先访问对象上显示拥有的属性,如果找不到,就会去对象的隐式原型中查找,也就是__proto__中查找,如果还是找不到就会在隐式原型的隐式原型中找,层层往上,直到找到null为止
// 祖父辈 - 拥有家族姓氏
const 祖父 = {
姓氏: '张',
说姓氏() {
console.log(`我们家族姓${this.姓氏}`);
}
};
// 父辈 - 继承了祖父的特性
const 父亲 = Object.create(祖父);
父亲.名字 = '建国';
父亲.自我介绍 = function() {
console.log(`我叫${this.名字}·${this.姓氏}`);
};
// 子辈 - 继承了父亲的特性
const 儿子 = Object.create(父亲);
儿子.名字 = '小明';
// 现在来看看查找过程
儿子.自我介绍(); // 查找过程如下:
// 1. 儿子自身有"名字"属性 → "小明"
// 2. 儿子自身没有"姓氏" → 查父亲的__proto__(祖父) → "张"
// 3. 儿子自身没有"自我介绍" → 查父亲 → 找到并执行
// 输出: "我叫小明·张"
儿子.说姓氏(); // 1. 儿子自身没有"说姓氏"
// 2. 查父亲 → 没有
// 3. 查祖父 → 找到并执行
// 输出: "我们家族姓张"
console.log(儿子.职业); // 1. 儿子自身没有
// 2. 父亲没有
// 3. 祖父没有
// 4. 祖父的__proto__是Object.prototype
// 5. Object.prototype的__proto__是null
// 最终返回undefined
没有原型的对象
- 没有原型的对象就是Object的实例,Object的原型是null
- Object.create(obj)创建一个新对象,让这个新对象的隐式原型等于传入的obj
- Object.create(null)得到一个没有原型的对象
const pureObj = Object.create(null);
// 验证
console.log(pureObj.toString); // undefined
console.log(Object.getPrototypeOf(pureObj)); // null
constructor
constructor 的存在是为了让所有实例对象都知道自己是从哪个构造函数创建的
function Person(name) {
this.name = name;
}
const alice = new Person('Alice');
// 通过 constructor 属性可以知道对象是由哪个构造函数创建的
console.log(alice.constructor === Person); // true