# 原型及原型链
# 前言
大家都知道,JavaScript是一种弱类型语言,和Java这种传统的面向对象语言不同,JS没有类的概念(ES6的class也只不过是语法糖,而不是真正意义上的类,其实质还是基于原型链)。在JavaScript中,一切皆是对象。
在基于类的传统面向对象的编程语言中,对象由类实例化而来,实例化的过程中,类的属性和方法会拷贝到这个对象中;对象的继承实际上是类的继承,在定义子类继承于父类时,子类会将父类的属性和方法拷贝到自身当中。因此,这类语言中,对象创建和继承行为都是通过拷贝完成的。
但在JavaScript中,对象的创建、对象的继承(更好的叫法是对象的代理,因为它并不是传统意义上的继承)是不存在拷贝行为的。
引用 掘金-艾特老干部
后面会放一张大家应该都见过的神图,神图出,仙魔退避。
# 什么是原型和原型链
JavaScript中的对象,都有一个内置属性[[Prototype]]
,指向这个对象的原型。对象的原型实际上也是一个对象,它和对象本身是完全独立的两个对象。
当查找一个属性或方法时,如果在当前对象中找不到定义,会继续在当前对象的原型对象中查找;如果原型对象中依然没有找到,会继续在原型对象的原型中查找(原型也是对象,也有它自己的原型);如此继续,直到找到为止,或者查找到最顶层的原型对象中也没有找到,就结束查找,返回undefined。
这个查找的过程是一层层不断的向[[Prototype]]
递进的,这个操作是一个链式的操作,而这些原型链接组成的链条,就是原型链。拥有相同原型的多个对象,他们的共同特征正是通过这个查找模式体现出来的。
比如函数的继承:
function person(){};
function person1(){};
person1.prototype = Object.create(person.prototype, {
constructor: {
value: person1
}
});
console.log(person1); // function person1(){}
new person1();
// person1.__proto__ = person
// person1.__proto__.constructor = function person1() {}
// person1.__proto__.__proto__.constructor = function person() {}
2
3
4
5
6
7
8
9
10
11
12
# 普通对象和函数对象
// 普通对象
var obj1 = {};
var obj2 = new Object();
var obj3 = new person();
// 函数对象
function person(){};
var person1 = function(){};
var person2 = new Function('aaa','console.warn(aaaa)');
console.log(typeof Object); //function
console.log(typeof Function); //function
console.log(typeof person); //function
console.log(typeof person1); //function
console.log(typeof person2); //function
console.log(person instanceof Function) // true
console.log(typeof person); //function
console.log(typeof person1); //function
console.log(typeof person2); //function
console.log(obj3 instanceof Object) // true
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
通过以上实例我们知道 new Function 构建的都是函数对象,其余都是普通对象
# 通过字面量方式创建对象
当通过字面量方式创建对象时,它的原型就是Object.prototype。
虽然我们无法直接访问内置属性[[Prototype]],但我们可以通过Object.getPrototypeOf()或对象的__proto__获取对象的原型。
var obj = {};
Object.getPrototypeOf(obj) === Object.prototype; // true
obj.__proto__ === Object.prototype; // true
2
3
# 通过构造调用创造对象
构造调用,经常被我们称为构造函数,是因为我们在其他语言中的习惯叫出来的。在JavaScript中同样没有构造函数的概念,所有的函数都是平等的,只不过用来创建对象时,函数的调用方式不同而已。
通过函数的构造调用创建对象,也是一种常见的创建对象的方式。
基于同一个函数创建出来的对象,理应可以共享一些相同的属性或方法,但这些属性或方法如果放在Object.prototype里,那么所有的对象都可以使用它们了,作用域太大,显然不合适。
于是,JavaScript在定义一个函数时,同时为这个函数定义了一个 默认的prototype属性,所有共享的属性或方法,都放到这个属性所指向的对象中。由此看出,通过一个函数的构造调用创建的对象,它的原型就是这个函数的prototype指向的对象。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayName = function () {
console.log(this.name)
}
var person1 = new Person('zhansan', 29);
var person2 = new Person('lisi', 29);
person1.sayName() // zhansan
person2.sayName() // lisi
person1.__proto__ === Person.prototype; // true
2
3
4
5
6
7
8
9
10
11
12
13
# Object.create()
还有一种常用的创建对象的方式是使用Object.create()。这个方法会以你传入的对象作为创建出来的对象的原型。
var obj = {};
var obj2 = Object.create(obj);
obj2.__proto__ === obj; // true
2
3
上面提到过的继承也是利用了Object.create();
通过Object.create(null)
可以创建一个空对象,即没有原型的对象。常常在优化的时候使用。
# proto,prototype,constructor
- proto: 在JavaScript权威指南中指出每个js对象一定对应一个原型对象,并从原型对象继承属性和方法
- prototype: 当创建函数对象时,js会自动为这个函数添加prototype属性(这里明确一下只有函数对象才会有此属性)
- 每个原型都有一个 constructor 属性指向关联的构造函数。
函数的prototype中还有一个constructor方法,建议大家就当它不存在,它的存在让JavaScript原型的概念变得更加混乱,而且这个方法也几乎没有作用。当然在上述处理继承的时候修改了constructor的值,不过希望大家还是要以实践为主,存在即合理,重点在于如何去使用它们。
__proto__指向当前对象的原型,prototype是函数才具有的属性,默认情况下,new 一个函数创建出的对象,其原型都指向这个函数的prototype属性。
如图可以加深理解:
虽然person1和person2这两个个实例都不包含属性和方法,但是我们可以通过查找对象属性来实现调用person1.sayName()
此时我们来确定两个实例对象返回的原型指针是否一样(Object.getPrototypeOf 此方法可以获取对象的原型)
Object.getPrototypeOf(person1) === Person.prototype // true
Object.getPrototypeOf(person1) === Person.prototype // true
2
通过如上输出结果得知他们内部都有一个指向Person.prototype的指针也就是
person1.__proto__ === Person.prototype
person2.__proto__ === Person.prototype
2
经过上面解释这么多我们得出的结果就是如下
person1.__proto__ === Person.prototype
Person.prototype.constructor == Person
2
instanceOf的原理实质上就是在原型链上查找是否存在原型一致的对象
# Object
- js几乎所有对象都是Object; 典型对象继承属性(包括方法)
- 所有引用类型的原型链上必然存在Object的原型(Object.create(null)除外)
- new Object 出来的实例是普通对象我们前面说过只有函数对象才有prototype
- Object 实际是function Object跟function Function 类似
- Object.prototype是Object构造函数的属性。它也是原型链的终结
function Object (name,age) {
this.name = name;
this.age = age;
}
Object.prototype.sayName = function () {
console.log(this.name)
}
object = new Object('zhangsan', 20)
object.sayName()
2
3
4
5
6
7
8
9
我们可以得出结论Object.prototype.proto === null
# null和undefined区别
- null === undefined为false,null == undefined为true 说明只是值相等
- null是一个表示”无”的对象,转为数值时为0;undefined是一个表示”无”的原始值,转为数值时为NaN。
- null表示变量未指向任何对象,undefined表示变量被声明但是没有被赋值(除非是主动赋值为undefined,这个时候是有意义的)
# Function
- 在js中每个函数实际上都是一个Function对象。
- 使用Function构造器生成的Function对象是在函数创建时解析的(
new Function('xxx')
),而其他函数方式是跟其他代码一起解析,所以较为低效 - 全局的Function对象没有自己的属性和方法,通过Function.prototype继承部分属性和方法。
- 我们说js中万物皆对象,Function也也是对象,只不过是函数对象所以
Function.prototype.__proto__
指针指向Object.prototype
Object.__proto__
指针指向Function.prototype
Function创建的函数一般在全局作用域中被创建,但并不会像其他函数一样产生闭包,所以只能自己内部和全局的变量
eg:
function Function (name,age){
this.name = name
this.age = age
}
Function.prototype.sayName = function () {
console.log(this.name)
}
f = new Function('zhangsan', 20)
f.sayName();
2
3
4
5
6
7
8
9
这里我们同样以图例的形式来说明Function的proto,prototype,constructor
我们可以看到 Object.prototype === Function.prototype.__proto__
# 将Function,Object,Person链接起来
我们把上面所说的图整合到一起是什么样子的呢?
通过以上图例我们可以观察到其实js原型链就是由相互关联的链条组成,查找属性或者方法的过程就是图中红色链条的过程,如果找到则终止否则直到返回null。
通过上面我们可以得出结论:
- 只有函数对象才有
prototype
,但是每个对象(普通对象和函数对象)都拥有proto
属性 - 原型对象都有一个
constructor
属性指向它们的构造函数(也就是自己) - 原生对象既是对象,也是构造函数
- 实例对象的隐式原型始终指向构造函数的显式原型(
person1.__proto__ > Person.prototype
) - 原型链的查找过程链接依赖
proto
指针逐级向上,并且原型链的尽头始终为null
# 神图
神图出,仙魔退避。
只要你完全整明白这张神图,那么原型链对你来说就是小case了。
下面上神图:
# 结语
掌握原型和原型链的相关知识,有助于我们更好的了解JavaScript本身。同时我们也要加强对于面向对象语言的了解,这样才能扩展我们的视野。
# 致谢
感谢大家阅读我的文章,如果对我感兴趣可以点击页面右上角,帮我点个star。
作者:前端小然子
链接: https://xiaoranzife.com/guide/jichu/prototype.html
来源:前端小然子的博客
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。