JavaScript 中的原型一直是我很害怕的一个主题,理由很简单,由于真的很差理解,但它确实是 JavaScript 中很重要的一部分,并且是面试的必考题,就算如今不懂,之后早晚有一天要把它弄懂,否则的话永远都没办法把本身的技术能力往上提升一个层次,因此今天就来说讲 JavaScript 中的原型。javascript
本文是这系列的第四篇,往期文章:java
首先要说一下为何会有原型这个东西,那是由于在 JavaScript 中并无 “类” 的概念,它是靠原型和原型链实现对象属性的继承,即使在 ES6 中新出了class
的语法,但那也只是一个语法糖,它的底层依然是原型。面试
要理解原型(原型链),最重要的是理解两个属性以及它们之间的关系:浏览器
__proto__
prototype
__proto__
JavaScript中,万物皆对象,全部的对象都有__proto__
属性(null
和undefined
除外),并且指向创造这个对象的函数对象的prototype
属性。闭包
var obj = {};
console.log( obj.__proto__ === Object.prototype ); // true
var arr = [];
console.log( arr.__proto__ === Array.prototype ); // true
var fn = function(){};
console.log( fn.__proto__ === Function.prototype ); // true
var str = "";
console.log( str.__proto__ === String.prototype ); // true
var num = 1;
console.log( num.__proto__ === Number.prototype ); // true
复制代码
前面说了,在 JavaScript 中,一切皆对象(能够理解为它们都是从对象那里继承过来的),因此:函数
console.log( Function.prototype.__proto__ === Object.prototype ); // true
console.log( Array.prototype.__proto__ === Object.prototype ); // true
console.log( String.prototype.__proto__ === Object.prototype ); // true
复制代码
而由于Object.prototype
的__proto__
已是终点了,因此它的指向是:post
console.log( Object.prototype.__proto__ === null ); // true
复制代码
注意,虽然大多数浏览器都支持经过__proto__
来访问,但它并非ECMAScript
的标准,在 ES5 中能够经过Object.getPrototypeOf()
来获取这个属性。ui
var obj = {};
console.log( Object.getPrototypeOf(obj) === Object.prototype ); // true
复制代码
prototype
prototype
是每一个函数对象都具备的属性(它也有__proto__
,由于函数也是对象),实例化建立出来的对象会共享此prototype
里的属性和方法(经过__proto__
)。this
在上面的例子中已经看到过prototype
的身影,下面经过一个例子来说述它的做用。spa
如今咱们有一个构造函数Person
,而且对它进行实例化:
function Person(name){
this.name = name;
this.sayName = function(){
console.log("个人名字是:" + this.name);
}
}
var a = new Person("小明");
var b = new Person("小红");
a.sayName(); // 个人名字是:小明
b.sayName(); // 个人名字是:小红
复制代码
可是,用构造函数生成实例对象,有一个缺点,那就是没法共享属性和方法。
例如上面例子中的a
和b
,它们都有sayName
方法,虽然作的事相同,但它们倒是独立的,这就会形成极大的资源浪费,由于每个实例对象,都有本身的属性和方法的副本。
考虑到这一点,Brendan Eich 决定为构造函数设置一个prototype
属性。
这个属性包含一个对象,全部实例对象须要共享的属性和方法,都放在这个对象里面,而不须要共享属性和方法,就放在构造函数里面,这个对象就是prototype
对象。
实例对象一旦建立,将自动引用prototype
对象的属性和方法。也就是说,实例对象的属性和方法,分红两种,一种是本地的,另外一种是引用的。
如今对上面的例子进行改写:
function Person(name){
this.name = name;
}
Person.prototype = {
sayName : function(){
console.log("个人名字是:" + this.name);
}
}
var a = new Person("小明");
var b = new Person("小红");
a.sayName() // 个人名字是:小明
b.sayName() // 个人名字是:小红
复制代码
如今不管Person
被实例化多少次,它的实例对象都共享同一个sayName
方法,这就是prototype
最大的用处。
讲原型一个不可避免的概念就是原型链,原型链是经过__proto__
来实现的。
如今咱们以Person
的例子来说整个原型链。
var a = new Person("小明");
// 实例化对象的 __proto__ 指针指向构造函数的原型
console.log( a.__proto__ === Person.prototype )
// 构造函数的原型是一个对象,它的 __proto__ 指向对象的原型
console.log( Person.prototype.__proto__ === Object.prototype )
// 函数也是一个对象,它的 __proto__ 指向 函数的原型
console.log( Person.__proto__ === Function.prototype )
// 函数的原型是一个对象,它的 __proto__ 指向对象的原型
console.log( Function.prototype.__proto__ === Object.prototype )
// 对象的原型的__proto__ 指向 null
console.log( Object.prototype.__proto__ === null )
复制代码
以上就是a
对象的整个原型链。
当访问一个对象的属性时,Javascript 会从对象自己开始往上遍历整个原型链,直到找到对应属性为止。若是此时到达了原型链的顶部,也就是上例中的 Object.prototype
,仍然未发现须要查找的属性,那么 Javascript 就会返回 undefined
值。
注:此文为原创文章,如需转载,请注明出处。