JS 对象原型链

简介:js的每个对象都继承另一个对象,后者称为原型对象。一方面,任何对象都可以充当其他对象的原型,另一方面,原型对象也是对象,所以它有自己的原型,null也可以充当原型,区别在于它没有自己的原型对象。

复习js中的类型

js的每一个值,都属于某一种数据类型,共有六中数据类型:

  • 数值(number):小数和整数
  • 字符串(string):字符组成的文本
  • 布尔值(boolean):true和false
  • undefined:未定义或不存在,即由于没有定义,暂时没有任何值
  • null:无值
  • 对象(object):各种值组成的集合

通常将数值、字符串、布尔值称之为原始类型的值,而将对象称之为合成类型。

对象又分为三个子类型:

  • 狭义对象(object)
  • 数组(array)
  • 函数(function)

typeof 运算符

typeof运算符可以返回一个值的数据类型。运算结果可能有下面三种情况

  • 原始类型:数值、字符串、布尔值分别返回numberstringboolean
  • 函数:函数返回function
  • undefined:undefined返回undefined(利用这一点可以用来检查一个没有声明的变量而不报错)
  • 其他的返回object:window{}[]null

可以看到数组也是特殊的对象。

null的类型也是object,是由于当初设计的时候没考虑到null,只把它当做object的一个特殊值(32位全部为0),这是历史原因,本质上null是一个类似于undefined的特殊值。

null 和 undefine的区别

和Java类似,null是可以转换为0的,js的设计者觉着用一个对象类型的null表达这个值很不好,最好是可以区分,所以就设计了undefinednull是一个表示的对象,可以转换为0undefined是一个表示无的原始值,转换为数值时为NaN

1
2
5 + null // 5
5 + undefined // NaN

原型链

js的每个对象都继承另一个对象,后者称为原型对象。一方面,任何对象都可以充当其他对象的原型,另一方面,原型对象也是对象,所以它有自己的原型,null也可以充当原型,区别在于它没有自己的原型对象。

每一个构造函数都有一个prototype属性,这个属性会在生成实例的时候,称为实例对象的原型对象。

函数在js中是很特殊的存在,是所谓的一等公民。当创建函数的时候,js会为这个函数自动添加protrotype属性,值是空对象。而一旦你把这个函数当做构造函数constructor调用(即通过new关键字调用)时,那么js就会帮你创建该构造函数的实例,实例继承构造函数prototype的所有属性和方法(实例通过设置自己的__prototype__指向构造函数的prototype来实现这种继承)。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 声明函数F1和F2
function F1(){}
function F2(){}
// 通过设置函数的prototype属性来给F1增加方法
F1.prototype.print = function(){console.log('F1');};
// 创建F1的实例
var a = new F1();
// 查看a是有print方法
a.print() // 输出F1
// 创建F2的实例
var b = new F2();
// 查看b没有print方法
p.print() // undefined

这个实例着重来解释实例通过设置自己的__proto__指向构造函数的prototype来实现原型继承,继承所有的属性和方法

构造函数通过prototype来存储需要共享的属性和方法,也可以设置prototype执行现存的对象来继承该对象。

也即是说,当实例对象本身没有某个属性或方法的时候,它会到构造函数的prototype属性指向的对象寻找改属性或方法。这就是原型对象的特殊之处。

原型对象的作用,就是定义所有实例对象共享的属性和方法。这也就是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出的子对象。

如果一层层往上追溯,所有对象最终都可以追溯到Object.prototype,即Object构造函数的prototype属性。相应的,Object对象有没有它的原型呢?有的,就是没有任何属性和方法的null对象,而null对象没有自己的原型。

原型链的作用是,读取到对象的某个属性时,js引擎先寻找对象本身属性,如果找不到,就到它的原型中去找,如果还找不到,就到原型的原型中去找,直到最顶层的Object.prototype还是找不到,就返回undefined

如果对象自身和原型都定义了同名属性,那么优先读取对象自身的属性。

Object.getPrototypeOf() 获得一个对象的原型

Object.getPrototypeOf()方法返回一个对象的原型,这是获取原型对象的标准方法

创建个Person类,并实例化

1
2
function Person(){this.name='';this.age = 0}
var p = new Person();

检测p对象的原型:

1
2
3
Object.getPrototypeOf(p);
// 输出
{constructor: {name: "person"}}

发现创建的实例对象p有属性constructor

1
p.constructor.name // "Person"

其实实例对象p自身没有contructor属性,改属性其实是读取原型链上Peson.prototype.constructor属性。

1
2
p.hasOwnProperty('constructor') // false
Person.prototype.hasOwnProperty('constructor'); // true

constructor属性的作用,是分辨原型对象到底属于哪个构造函数。constructor属性表示原型对象与构造函数之间的关联关系,如果修改了原型对象,一般也会同事修改constructor属性,防止引用时出错。

总结:作为构造函数的Person有属性prototypeprototype里面有构造函数的属性constructor。作为实例对象p没有prototype属性,但是可以调用原型链上的constructor属性,这个应该是继承在属性__proto__上,和p.__proto__.constructor.name一个效果。

参考博客:
https://github.com/mqyqingfeng/Blog/issues/2
http://yujiangshui.com/javascript-prototype-and-create-object/
https://github.com/creeperyang/blog/issues/9

有钱的捧个钱场,没钱的捧个人场~