原型和原型链:看完突然觉得自己又行了
2、原型
上篇文章我们聊到了构造函数,不太了解的构造函数的话可以 look look! 点击这里
说到了构造函数,怎么能不提一下原型,那原型链呢?下面走起
2.1 构造函数原型 prototype
构造函数通过原型分配的函数是所有对象所共享的
js规定,每一个构造函数都有一个 prototype
属性,指向另一个对象。
注意:这个 prototype
就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有
我们可以把那些不变的方法,直接定义在 prototype
对象上,这样所有对象的实例就可以共享方法了
这里我们先总结一下:
- 原型是什么?
一个对象,我们称之为 prototype
的原型对象
- 原型的作用是什么?
共享方法
function Person(namer, age) {
this.namer = namer
this.age = age;
}
// 往prototype对象里面添加say方法
Person.prototype.say = function() {
console.log(this.namer + this.age + '岁了');
}
var p1 = new Person('张三', 2);
var p2 = new Person('李四', 3);
p1.say(); // 张三2岁了
console.log(p1.say === p2.say); // true
一般情况下,我们的公共属性定义到构造函数里面,公共的方法我们放到原型对象上
2.2 对象原型 __proto__
每一个对象都有一个属性 __proto__
,指向构造函数的 prototype
原型对象,我们的对象之所以可以使用构造函数 prototype
原型对象的属性和方法,就是因为对象有 __proto__
隐形的存在
__proto__
对象原型的意义就在于为对象的查找机制提供一个方法或者一个路线,但是它是一个非标准属性,因此在实际开发中,可以使用这个属性,它只是内容指向原型对象prototype
所以:
console.log(p.__proto__ === Person.prototype); // true
方法say
的查找规则是:
- 首先看p对象上是否有
say
方法,如果有就执行这个对象上的say()
- 如果没有,但是因为有
__proto__
的存在,就去构造函数原型对象prototype
身上去查找say()
这个方法
2.3 constructor
构造函数
对象原型(__proto__
)和 构造函数的原型对象(prototype
)里面都有一个 constructor
属性,这个我们称之为构造函数,因为它指回构造函数本身
console.log(Person.prototype.constructor);
console.log(p.__proto__.constructor);
/* Person(name, age) {
this.name = name;
this.age = age;
} */
构造函数、原型对象、对象实例三者之间的关系:
如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,那么必须手动利用 constructor
指回原来的构造函数
function Person(namer, age) {
this.namer = namer
this.age = age;
}
Person.prototype = {
// 指回构造函数
constructor: Person,
say: function() {
console.log(this.namer + this.age + '岁了');
}
}
var p = new Person('张三', 2);
p.say(); // 张三2岁了
console.log(p.__proto__.constructor);
console.log(Person.prototype.constructor);
/* Person(namer, age) {
this.namer = unamer;
this.age = age;
} */
.
2.4 原型链
实例对象继承了其构造函数的原型对象,原型对象也是对象,它也继承了它构造函数的原型对象,所以,实例对象 和 原型对象之间就形成一条链路 —— 原型链
所以对象最终都会指向内置的 Object.prototype
—— 对象原型,并且它会指向一个空的null
,也就是原型链的顶端
整和一下,看一下完整的原型链
function Person() {}
var p = new Person()
console.log(p.__proto__ === Person.prototype); // ture
console.log(p.__proto__.__proto__ === Object.prototype); // true
console.log(p.__proto__.__proto__.__proto__ === null); // true
3、js的成员查找机制
规则:
- 当访问一个对象的属性时,首先检查整个对象有没该属性,有的话就直接返回
- 如果没有就查找它的原型,也就是
__proto__
指向的prototype
原型对象 - 如果还没有就查找原型对象的原型(
Object
的原型对象) - 顺着原型链一直找
Object
为止
请看下面代码:
function Person() {}
Person.prototype.say = function() {
console.log("我在构造函数的原型对象上");
}
var p = new Person()
p.say()
执行过程:
- 先实例化对象p(执行
new
),然后调用say
方法,发现实例对象上并没有say
这个方法 - 但是没有关系,还不会报错,通过
__proto__
属性到Person
的原型对象去查找 - 果然在这里找到了我想要的
say
方法,于是就返回该方法
由于有原型链,我们还可以把它定义在Object
的原型对象上
Object.prototype.say = function() {
console.log("我在Object的原型对象上");
}
通过 p.say()
一样可以访问到say
方法,而且定义在Object
原型对象上的方法,是对所有对象共享的,直接 say()
一样可以访问到
还记得字符串的属性和方法吗?我们都知道,属性和方法是对象才有的,为什么一个小小的字符串就会有这么多的属性和方法呢?
答案就是:原型链
在一切皆对象的js中,字符串的原型对象就是 String
对象,所以当我们对字符串使用一些属性和方法时,会优先在字符串本身上查找,结果当然是没有找到咯,所以它会通过 __proto__
到字符串的原型对象String
上去查找,最后在String
对象上找了它想要的方法。所以,我们可以对字符串使用String
对象上存在的属性和方法
为了证明我们没瞎说,下图为证(部分截图)
可以在上面看到我们熟悉的属性和方法,可以说只要有原型链在,String
对象上所有的方法,我们都可以用在字符串
不仅字符串,数字型、布尔值型都是这样的,原型链附身,都可以使用其自身对象的方法,下图是Number
对象
4.1 查找原型**instanof
**
instanof
操作符有两个作用:
- 判断类型
- 可以用来检测某个实例对象的原型链上是否存在构造函数的
prototype
属性
function Person1() {}
function Person2() {}
Person2.prototype = new Person1()
var p1 = new Person1()
var p2 = new Person2()
console.log(p1 instanceof Person1); // true
// p1对象的原型链并不存在Person2
console.log(p1 instanceof Person2); // false
console.log(p2 instanceof Person2); // true
console.log(p2 instanceof Person1); // true
p2是 Person1
和Person2
的实例,因为p2的原型链中包含了这两个构造函数的原型
instanceof
实现原理就是变量原型链,查找原型链上所有显示原型(prototype
) 是否等于隐式原型(__proto__
),直至找到原型链的最顶层