js原型与原型链

一、JS原型与原型链

(1)JS原型

①函数对象

​ 所有引用类型(Object,Array,Date,Function),都拥有__ proto__(隐式原型)属性,属性值为一个普通的对象。

​ 所有函数都拥有prototype属性(显示原型),为函数所特有的属性。

​ 原型对象:即prototype属性指向的对象叫做原型对象,当申明一个函数的时候,编译器会自动帮你创建一个与之对应的对象称为原型对象。 每个函数都有一个prototype属性,它是一个指针,指向一个对象,这个对象包含了所有实例共享的属性和方法。

②构造函数

​ 首先先构造一个函数。

1
2
3
4
function Person(name,age){
this.name=name;
this.age=age;
}

​ 往原型对象里面添加成员。

1
2
3
4
5
6
Person.prototype.eat=function(){
console.log('我要干饭!');
};
Person.prototype.study=function(){
console.log('我要学习!');
};

③实例化对象

通过new关键字,构建p1,p2实例对象。

1
2
let p1 = new Person('张三',18);
let p2 = new Person('李四',20);

此时p1,p2可以访问构造函数的原型上的方法,并且p1.eat === p2.eat。

1
2
p1.eat();   //输出"我要干饭!"
console.log(p1.eat===p2.eat); //输出:true

但注意如果没有使用往原型对象里面添加成员的方法,直接在构造函数内添加方法,在通过实例化对象调用该方法,此时二者不相等

1
2
3
4
5
6
7
8
9
10
11
function Person(name,age){
this.name=name;
this.age=age;
this.study=function(){
console.log('我要学习!');
}
}

let p1 = new Person('张三',18);
let p2 = new Person('李四',20);
console.log(p1.study===p2.study); //此时输出:false

这是因为,构造函数的定义方法在实例对象上都创建一遍,上方代码p1和p2都有名为study的方法,但是这两个方法不是同一个Function的实例。实例共享的属性和方法都放在原型对象上,有效解决构造函数内存资源浪费 + 全局变量污染。

__proto__属性

__proto__: 属于实例对象,可以让实例对象访问原型对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(name,age){
this.name=name;
this.age=age;
}
Person.prototype.eat=function(){
console.log('我要干饭!');
};
Person.prototype.study=function(){
console.log('我要学习!');
};

let p1 = new Person('张三',18);
console.log(p1.__proto__===Person.prototype); //此时输出:true

图1

⑤constructor属性

constructor属性,属于原型对象,指向构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Person(name,age){
this.name=name;
this.age=age;
}
Person.prototype.eat=function(){
console.log('我要干饭!');
};
Person.prototype.study=function(){
console.log('我要学习!');
};

let p1 = new Person('张三',18);
console.log(Person.prototype.constructor===Person); //输出为:true
console.log(p1.constructor===Person.prototype.constructor); //输出为:true

p1.__proto__.constructor可以直接写为p1.constructor,p1可以直接访问原型对象。

图2

(2)js原型链

①引用一个例子

原型链:每一个对象都有原型,而原型也是对象,也会有自己的原型。以此类推形成链式结构,称之为原型链。

观察以下代码输出结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Person(name,age){
this.name=name;
this.age=age;
}
Person.prototype.eat=function(){
console.log('我要干饭!');
};
Person.prototype.study=function(){
console.log('我要学习!');
};

let p1 =new Person('张三',18);

console.log(p1.name); //输出:张三
console.log(p1.age); //输出:18
p1.eat(); //输出:我要干饭!
p1.toString(); //不报错
p1.learn(); //报错

引用p1.eat(),p1的构造函数的原型对象有eat方法,正常输出。但是p1的构造函数的原型对象上没有learn和toString方法,引用p1.learn(),输出报错,调用p1.toString()输出却不报错。这是因为,当调用p1.toString()方法时,p1的构造函数的原型对象上没有toString方法,但是p1的构造函数的原型对象的原型对象上有toString方法。

图3

检查p1原型的原型。

图4

检查p1原型的原型的原型。

图5

发现原型链终点为null

②画出原型链。

图6

二、总结

(1)、我们需要牢记两点:①__proto__constructor属性是对象所独有的;②prototype属性是函数所独有的,因为函数也是一种对象,所以函数也拥有__proto__constructor属性。

(2)、prototype属性的作用就是让该函数所实例化的对象们都可以找到公用的属性和方法,即p1.__proto__ === Person.prototype

(3)、__proto__属性的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(父对象)里找,一直找,直到__proto__属性的终点null,再往上找就相当于在null上取值,会报错。通过__proto__属性将对象连接起来的这条链路即我们所谓的原型链。

(4)、constructor属性的含义就是指向该对象的构造函数,所有函数(此时看成对象了)最终的构造函数都指向Function。


js原型与原型链
http://example.com/2022/09/21/js原型与原型链/
作者
AlongSunsea
发布于
2022年9月21日
许可协议