原型和原型链:看完突然觉得自己又行了

吐槽君 分类:javascript

2、原型

上篇文章我们聊到了构造函数,不太了解的构造函数的话可以 look look! 点击这里

说到了构造函数,怎么能不提一下原型,那原型链呢?下面走起

2.1 构造函数原型 prototype

构造函数通过原型分配的函数是所有对象所共享的

js规定,每一个构造函数都有一个 prototype属性,指向另一个对象。

注意:这个 prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有

我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享方法了

这里我们先总结一下:

  1. 原型是什么?

一个对象,我们称之为 prototype 的原型对象

  1. 原型的作用是什么?

共享方法

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

img

所以:

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;
} */
 

构造函数、原型对象、对象实例三者之间的关系:

img

如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,那么必须手动利用 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,也就是原型链的顶端

img

整和一下,看一下完整的原型链

img

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的成员查找机制

规则:

  1. 当访问一个对象的属性时,首先检查整个对象有没该属性,有的话就直接返回
  2. 如果没有就查找它的原型,也就是 __proto__ 指向的 prototype 原型对象
  3. 如果还没有就查找原型对象的原型(Object的原型对象)
  4. 顺着原型链一直找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对象上存在的属性和方法

为了证明我们没瞎说,下图为证(部分截图)

img

可以在上面看到我们熟悉的属性和方法,可以说只要有原型链在,String对象上所有的方法,我们都可以用在字符串

不仅字符串,数字型、布尔值型都是这样的,原型链附身,都可以使用其自身对象的方法,下图是Number对象

img

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是 Person1Person2的实例,因为p2的原型链中包含了这两个构造函数的原型

instanceof实现原理就是变量原型链,查找原型链上所有显示原型(prototype) 是否等于隐式原型(__proto__),直至找到原型链的最顶层

回复

我来回复
  • 暂无回复内容