前言
此文参照了阮一峰大神在自己博客写的几篇关于JS继承原理的文章,在此直接放出连接:
JS一切皆是对象?
JS中一切皆是对象吗?这句话其实是错的,首先从设计语言上来考虑,如果一切皆是对象,那就不会分这么多基础类型了,那为什么非对象类型的其他类型值可以调用对象API呢,这里就引出了JS设计核心之一的原型与原型链
什么是原型(prototype)
1、
要解决这个问题,就得清楚会设计原型(prototype)这个属性。
首先带来一个自阮一峰博客的例子
function DOG(name){
this.name = name;
}
这里我们把他改成我们能理解的样子(有关this,详细请参阅this的MDN docs)
function 狗(名字){
this.名字 = 名字;
}
当我们运行new+“狗”,并填入“名字”时,我们就得到了一个狗对象的实例
第一只狗 = new 狗(大黄);
第一只狗.名字 //大黄
我们把“狗”称为“第一只狗”的构造函数(consturctor),而“第一只狗”称为"狗"对象的实例
所谓"构造函数",其实就是一个普通函数,但是内部使用了this变量。对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上。
2、
但是,用构造函数生成实例对象,有一个缺点,那就是无法共享属性和方法。
比如,在“狗”对象的构造函数中,设置一个实例对象的共有属性“毛色”。
function 狗(名字){
this.名字 = 参数名字;
this.毛色= "黄色"
}
然后我们生成两个“狗”实例
第一只狗 = new 狗(大黄);
第二只狗 = new 狗(二黄);
第一只狗.毛色 //黄色
第二只狗.毛色 //黄色
这两个对象的“毛色”属性是独立的,修改其中一个,不会影响到另一个,每一个实例对象,都有自己的属性和方法的副本,这不仅无法做到数据共享,也是极大的资源浪费。
//这里把第一只狗的毛色修改成黑色
第一只狗.毛色= "黑色"
console.log(第二只狗.毛色); //黄色
//这里可以看到第二支狗的毛色依然是黄色
3、
根据这一点,才有了如今的prototype属性,prototype属性是一个对象,里面包含了所有需要共享的属性和方法,而不需要共享的属性和方法,则放在构造函数里。
实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用的。
下面通过设置“狗”.prototype属性来改写之前的例子
function 狗(名字){
this.名字 = 名字;
}
狗.prototype = {"毛色" = "黄色"}
第一只狗 = new 狗(大黄);
第二只狗 = new 狗(二黄);
console.log(第一只狗.毛色) // 黄色
console.log(第二只狗.毛色) // 黄色
如果现在对狗.prototype进行修改,则第一只狗和第二只狗的毛色会一起发生变化
狗.prototype = {"毛色" = "黑色"}
console.log(第一只狗.毛色)
// 黑色
console.log(第二只狗.毛色)
// 黑色
什么是原型链
1、
无论是什么类型的值,在实例化之后都会带有一个"_ _ proto_ “的属性,该属性指向其继承而来的上一级的prototype,在此把” _ proto_ _"称为子原型,prototype称为父原型。
下面以实例化的数字“1”为例
var num = 1; //
num.__proto__ === Number().prototype;
num.__proto__.__proto__ === Number().prototype.__proto__ === Object.prototype
num.__proto__.__proto__.__proto__ ===
Number().prototype.__proto__ ===
Object.prototype.__proto__ === null
从以上例子可以看出,由于Object.prototype.__ proto __ 的值为null,意味着Object.prototype没有相对的父原型来继承,所以Object.prototype可以认为是最顶级的父原型,所有的子原型都继承于Object.prototype,我们这种子原型引用父原型的关系,称之为原型链。
因为Object.prototype是最顶级的父原型,所有类型的值都能调用或者临时调用其存储的方法和属性,所以才有“JS一切皆是对象”的说法,其实这种说法是完全错误的,比较严谨的说法应该是“JS内一切类型的数值都能视为对象”。