简言
原型和原型链是面试中高级基本都会问的面试题,所以想要要个一个高的工资一定要理解,其实谈不上理解,只要记住以下几点,然后进行举例说明面试这个面试题就算过了。
我主要是通过这个文章并加入自己的理解写了这篇文章:感兴趣的朋友可自行下载:
JavaScript深入系列目录地址:github.com/mqyqingfeng…。
JavaScript深入系列预计写十五篇左右,旨在帮大家捋顺JavaScript底层知识,重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。
如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎star,对作者也是一种鼓励。
首先我们需要先了解几个事情:
- JavaScript对每个创建的对象都会设置一个原型,指向它的原型对象。
- 当我们用
obj.xxx访问一个对象的属性时,JavaScript引擎先在当前对象上查找该属性,如果没有找到,就到其原型对象上找,如果还没有找到,就一直上溯到Object.prototype对象,最后,如果还没有找到,就只能返回undefined。 3.每个函数都有一个 prototype 属性,就是我们经常在各种例子中看到的那个 prototype
我们先创建一个构造函数并创建实例对象。方便理解
function Person() {
}
var person = new Person();
person.name = 'Kevin';
console.log(person.name) // Kevin
console.log(person)
进入正题
我觉得原型链就是几个属性组成的关系图,我们挨个拆解看其实不难。如果整篇文章你看完还是没有懂,建议你再看一遍或者再找一篇文章,总有一篇文章能让你理解。先让你有个大体的了解直接给你上图看一下整个原型链和person的打印结果
顺便还要说一下,图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。
看不懂没关系,先有个了解。接一下我们挨个拆解看
###prototype
prototype翻译就是原型的意思,那么根据上面创建的实例对象person的原型是什么? 我们来上张图来简单记住实例的原型是什么。
根据这个图,说白了person的原型就是它构造函数的prototype,也就是Person.prototype。从上面的person对象打印也可以看出。
那我们知道了原型,那原型究竟是什么呢?
你可以这样理解:每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。
白话:Person.prototype这个原型是个对象会被person这个实例对象所关联,原型上每个属性会被实例对象所"继承"。
没看懂没关系,来我们举个栗子:
function Person(){
}
Person.prototype.name = 'xiaoMing'
var person = new Person()
console.log(person.name) //xiaoMing
我们明明没有在构造函数和对象中定义name的属性最后却能输出。为什么?
因为原型链,上面说到JavaScript引擎先查找当前对象,如果当前没有,他根据原型链一路向上查找,如果都没有查找到会返回undefined
注意:只有函数才有prototype
注意:只有函数才有prototype
注意:只有函数才有prototype
主要的事情说三遍。
可能这里有朋友会有疑惑,如果我简单的创建对象var obj = {},它的原型是什么?
这里大家可以转换以下思路,var obj = {}咱们换个写法写成var obj = new Object()这样按照我的说法obj的原型就是Object.prototype。接下来我们说第二个属性
###__proto__
这是每一个JavaScript对象(除了 null )都具有的一个属性,叫__proto__,这个属性会指向该对象的原型。
这个属性就没什么太多说的,直接上栗子,然后自己敲一下在控制台看一下理解以下
//还是那个函数
function Person(){
}
var person = new Person()
console.log(person.__proto__ === Person.prototype) // true
在给你来张关系图
既然实例对象和构造函数都可以指向原型,那么原型是否有属性指向构造函数或者实例呢?
###constructor
指向实例倒是没有,因为一个构造函数可以生成多个实例,但是原型指向构造函数倒是有的,这就要讲到第三个属性:constructor,每个原型都有一个 constructor 属性指向关联的构造函数。
为了验证这一点,我们可以尝试:
function Person() {
}
console.log(Person === Person.prototype.constructor); // true
更新下关系图
综上所述我们得出:
function Person() {
}
var person = new Person();
console.log(person.__proto__ == Person.prototype) // true
console.log(Person.prototype.constructor == Person) // true
// 顺便学习一个ES5的方法,可以获得对象的原型
console.log(Object.getPrototypeOf(person) === Person.prototype) // true
上面说到Person.prototype是个对象和(除null外)每个对象都有prototype,那么Person.prototype是不是还有原型
### 原型的原型
直接上图
从图上看出Person.prototype是由原型,那么原型是什么呢?
首先我们知道Person.prototype就是一个对象,那我们举个简单对象创建栗子
var obj = new Object()
obj.name = 'Kevin'
console.log(obj.name) // Kevin
//
根据上面所述obj的原型就是Object.prototype,所以Person.prototype的原型是Object.prototype。 我们也可以用上面学的__proto__去验证这一点
function Person() {
}
var person = new Person();
console.log(Person.prototype.__proto__ === Object.prototype) //true
那 Object.prototype 的原型呢?
Object.prototype.原型是null
那我们这边也用__proto__去验证这一点
function Person() {
}
var person = new Person();
console.log(Object.prototype.__proto__ === null) //true
最后再重新看一下整个关系图
顺便还要说一下,图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。
补充
最后,补充三点大家可能不会注意的地方:
constructor
首先是 constructor 属性,我们看个例子:
function Person() {
}
var person = new Person();
console.log(person.constructor === Person); // true
当获取 person.constructor 时,其实 person 中并没有 constructor 属性,当不能读取到constructor 属性时,会从 person 的原型也就是 Person.prototype 中读取,正好原型中有该属性,所以:
person.constructor === Person.prototype.constructor
__proto__
其次是 __proto__ ,绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于 Person.prototype 中,实际上,它是来自于 Object.prototype ,与其说是一个属性,不如说是一个 getter/setter,当使用 obj.__proto__ 时,可以理解成返回了 Object.getPrototypeOf(obj)。
真的是继承吗?
最后是关于继承,前面我们讲到“每一个对象都会从原型‘继承’属性”,实际上,继承是一个十分具有迷惑性的说法,引用《你不知道的JavaScript》中的话,就是:
继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。