概念
原型和原型链这俩小妖精绝对是js学习路上的一块绊脚石~如果能深入理解原型和原型链这两个概念绝对能让我们深入理解js
原型
- 原型是
function
对象的一个属性,它定义了构造函数制造出来的对象的公共祖先 - 通过构造函数产生的对象,可以继承该原型的属性和方法
- 原型也是对象 ```javascript // Person.prototype 原型 // Person.prototype = {} 祖先
// 定义Person构造函数 function Person(){} // Person的原型 Person.prototype
// 定义函数的属性 Person.prototype.num = 12; Person.prototype = { name: “小明”, age: 18 }
// 定义原型上的方法 Person.prototype.fun = function(){ console.log(‘原型方法’); }
<a name="qda7n"></a>
## prototype
`prototype` 能找到构造函数的原型,我们可以通过 `Person.prototype` 找到它的原型,并且能在它的原型上设置公共的属性和方法。
```javascript
// 定义原型上的属性
Car.prototype = {
name: "小明",
age: 18
}
// 构造函数
function Car() { }
let car = new Car()
// 通过Prototype直接找到了Person的原型上面的属性
console.log(Car.prototype);
/**
* {name: "小明", age: 18, fun: ƒ}
* age: 18
* fun: ƒ ()
* name: "小明"
* __proto__: Object
* */
proto
__proto__
属性的指向所创建它的原型对象。通过 new
创建出来的实例,可以通过 __proto__
属性直接找到创建该实例的原型对象。
具体的proto在下面的原型链中介绍
// 定义原型上的属性
Car.prototype = {
name: "小明",
age: 18
}
// 构造函数
function Car() { }
let car = new Car()
// 实例通过__proto__直接找它的原型
console.log(car.__proto__);
/**
* {name: "小明", age: 18, fun: ƒ}
* age: 18
* fun: ƒ ()
* name: "小明"
* __proto__: Object
* */
原型图解
通过下图可以看出构造函数 Person.prototype
和实例的 person1.__proto__
都是指向原型对象 Person.prototype
。也就是说他们两是对等的,那我们测试一下
console.log(Car.prototype == car.__proto__); // true
原型链
JavaScript的原型对象自身也是一个对象,它也有自己的原型对象,通过
__proto__
属性层层上溯,就形成了一个类似链表的结构,这就是原型链
。原型链的顶层是Object
对象,如果你还想往上找,那么找到的将会是null
。
new的作用
- 创建一个空的简单JavaScript对象(即{});
- 链接该对象(即设置该对象的构造函数)到另一个对象 ;
- 将步骤1新创建的对象作为this的上下文 ;
- 如果该函数没有返回对象,则返回this。
其实我们在new的第一步,创建是空对象。其实不然,而是创建的是一个 只有__proto__属性
的空对象。
再谈proto
如果我们想要了解原型链就要先了解 __proto__
属性。我们在使用某些属性的时候,发现自己本身没有这个属性,通过 __proto__
一层一层在往上查找,直到找到 Object
对象。如果还要拼命往上找那么 null
就会出现在眼前~
function Person() {
this.name = '我是构造函数 - 前端伪大叔'
}
Person.prototype.protoName = '我是原型 - 前端伪大叔';
let person = new Person();
console.log(person.name); // 我是构造函数 - 前端伪大叔
console.log(person.protoName); // 我是原型 - 前端伪大叔
沃特~按理说Person构造函数上没有protoName属性,为什么会找到Person.prototype原型呢?那么开始我们的主题。
原型查找
console.log(person.__proto__); // {protoName: "我是原型 - 前端伪大叔", constructor: ƒ}
console.log(person.__proto__.protoName); // 我是原型 - 前端伪大叔
我们看到了, __proto__
找到了 Person
的原型,直接在他的原型上找到的protoName。我们再来
原型链查找
console.log(person.toString); // toString() { [native code] }
console.log(person.__proto__.__proto__); // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
console.log(person.__proto__.__proto__.toString); // toString() { [native code] }
我们在定义方法的时候并没有定义 toString
方法,但是它出来了~为什么呢?因为我们通过 person.__proto__.__proto__
找到原型链顶层的 Object
对象,在 Object
对象上发现有 toString()
方法。所以person.tostring是调用的 Object.toString()
方法。这就是原型链的魅力~。
原型链的顶层
还有一种情况,如果我们再次调用 __proto__
那么将会是null,因为 Object
对象是原型链的顶层, Object
上不存在proto
console.log(person.__proto__.__proto__); // 上面没有__proto__ 所有下面输出是null
console.log(person.__proto__.__proto__.__proto__); // null
原型链图解
原型链模拟
通过继承实例的方式,模拟原型链查找方便里面原型链是如何查找。
// 原型链模拟
function Grandpa() {
Grandpa.prototype.gName = "叶叶"
}
let grandpa = new Grandpa()
// 继承上面
Father.prototype = grandpa;
function Father() {
this.fName = "巴巴"
}
let father = new Father();
// 继承于上面
My.prototype = father;
function My() {
this.mName = '我'
}
let my = new My()
// 自己有 用自己属性
console.log(my.mName); // 我
// 自己没有查找父亲属性
console.log(my.fName); // 巴巴
// 自己和父亲都没有查找祖先属性
console.log(my.gName); // 叶叶
构造器
至于我们在上一直看到的
constructor
是什么呢?其实他就是个构造器。可以通过原型查找到它的构造函数
。
function Person() {
this.name = '前端伪大叔'
}
// 原型查找构造函数
console.log(Person.prototype.constructor);
console.log(Person);
// ƒ Person() {
// this.name = '前端伪大叔'
// }
// 查看原型链顶层Object对象的构造函数
console.log(Object.prototype.constructor);
console.log(Object);
// ƒ Object() { [native code] }
// 实例通过__proto__同样可以找到创建自己的构造函数
console.log(person.constructor);
console.log(person.__proto__.constructor);
// ƒ Person() {
// this.name = '前端伪大叔'
// }