一直不清楚显式声明和隐式原型的关系,整理了一下相关疑惑,并且整理了
new和instanceof关于显式声明和隐式声明的相关知识
1 概念
1.1 显式原型
prototype — 显式原型 (explicit prototype property),每一个函数在创建之后都会拥有一个名为prototype的属性,这个属性指向函数的原型对象。
请注意通过Function.prototype.bind方法构造出来的函数是个例外,它没有prototype属性。
NOTE Function objects created using Function.prototype.bind do not have a prototype property or the [[Code]], [[FormalParameters]], and [[Scope]] internal properties. —– ECMAScript Language Specification
这个属性是一个指针,指向一个对象,它是显示修改对象的原型的属性。
1.2 隐式原型
__proto__ — 隐式原型 (implicit prototype link):JS中任意对象都有一个内置属性[[prototype]],是JS内部使用寻找原型链的属性在ES5之前没有标准的方法访问这个内置属性,但是大多数浏览器都支持通过__proto__来访问。
ES5中有了对于这个内置属性标准的Get方法Object.getPrototypeOf(),即Object.getPrototypeOf(obj)===obj.__proto__,用chrome和FF都可以访问到对象的__proto__属性,IE不可以。
隐式原型指向创建这个对象的函数(constructor)的prototype。
但是请注意Object.prototype.__proto__ //null,即Object对象的显式原型的隐式原型是null,这个属于特例。
根据ECMA定义 to the value of its constructor’s "prototype",指向创建这个对象的函数的显式原型。
所以关键的点在于找到创建这个对象的构造函数,接下来就来看一下JS中对象被创建的方式,一眼看过去似乎有三种方式:
- 对象字面量的方式
- new的方式
- ES5中的- Object.create()
但是本质上只有一种方式,也就是通过new来创建。为什么这么说呢,字面量的方式只是一种为了开发人员更方便创建对象的一个语法糖,本质就是 var o = new Object(); o.xx = xx;o.yy=yy;。
Object.create()是ES5中新增的方法,在这之前这被称为原型式继承。其实本质依然是通过new来创建的,如下面栗子中其实和new Boolean(),效果是比较相似的。
不同之处在于由 Object.create() 创建出来的对象没有构造函数,因为create()接收的就是一个内建函数的显式声明,如下栗子中,创建出来的对象的隐式声明其实是指向传入的实参,即ss.__proto__===Boolean.prototype。
| 1 | var ss =Object.create(Boolean.prototype) | 
2 作用
2.1 显式原型
用来实现基于原型的继承与属性的共享。
ECMAScript does not use classes such as those in C++, Smalltalk, or Java. Instead objects may be created in various ways including via a literal notation or via constructors which create objects and then execute code that initialises all or part of them by assigning initial values to their properties. Each constructor is a function that has a property named “prototype” that is used to implement prototype-based inheritance and shared properties.Objects are created by using constructors in new expressions; for example, new Date(2009,11) creates a new Date object. —-ECMAScript Language Specification
2.2 隐式原型
构成原型链,同样用于实现基于原型的继承。
举个例子,当我们访问obj这个对象中的x属性时,如果在obj中找不到,那么就会沿着__proto__依次查找。
Every object created by a constructor has an implicit reference (called the object’s prototype) to the value of its constructor’s “prototype” —-ECMAScript Language Specification
3 联合案例
| 1 | //新建测试变量 | 
3.1 显式声明的隐式声明
除了undefined和null对象类型之外,对象的隐式声明(__proto__)都是{},因为内建对象(built-in object)的显式声明和实例对象的显式声明都是一个对象。比如Array.prototype和temp_F.prototype都是一个对象,所以它们的隐式声明都是{},指向的都是Object这个构造体的显示声明,即Object.prototype。
请注意Object.prototype.__proto__和Function.prototype.__proto__是一个特例。
| 变量 | 使用方法 | 结果 | 
|---|---|---|
| Object | Object.prototype | {} | 
| Boolean | Boolean.prototype | [Boolean: false] | 
| Number | Number.prototype | [Number: 0] | 
| String | String.prototype | [String: ‘’] | 
| Array | Array.prototype | [] | 
| Date | Date.prototype | Date {} | 
| Function | Function.prototype | [Function] | 
| Object | Object.prototype.__proto__ | null | 
| Boolean | Boolean.prototype.__proto__ | {} | 
| Number | Number.prototype.__proto__ | {} | 
| String | String.prototype.__proto__ | {} | 
| Array | Array.prototype.__proto__ | {} | 
| Date | Date.prototype.__proto__ | {} | 
| Function | Function.prototype.__proto__ | [Function] | 
| temp_O | temp_O.prototype.__proto__ | Cannot read property __proto__of undefined | 
| temp_B | temp_B.prototype.__proto__ | Cannot read property __proto__of undefined | 
| temp_N | temp_N.prototype.__proto__ | Cannot read property __proto__of undefined | 
| temp_S | temp_S.prototype.__proto__ | Cannot read property __proto__of undefined | 
| temp_A | temp_A.prototype.__proto__ | Cannot read property __proto__of undefined | 
| temp_D | temp_D.prototype.__proto__ | Cannot read property __proto__of undefined | 
| temp_F | temp_F.prototype.__proto__ | {} | 
3.2 隐式声明的显式声明
除了undefined和null对象类型之外,所有对象的显式声明(prototype)都是undefined,因为内建对象(built-in object)的隐式声明和实例对象的隐式声明都是一个对象。比如Array.__proto__和temp_F.__proto__都是一个对象,所以它们的显示声明都是undefined。
构造函数因为都是Function()的实例,因此构造体的隐式声明也就都指向Function.prototype。
| 变量 | 使用方法 | 结果 | 
|---|---|---|
| Object | Object.__proto__ | [Function] | 
| Boolean | Boolean.__proto__ | [Function] | 
| Number | Number.__proto__ | [Function] | 
| String | String.__proto__ | [Function] | 
| Array | Array.__proto__ | [Function] | 
| Date | Date.__proto__ | [Function] | 
| Function | Function.__proto__ | [Function] | 
| Object | Object.__proto__.prototype | undefined | 
| Boolean | Boolean.__proto__.prototype | undefined | 
| Number | Number.__proto__.prototype | undefined | 
| String | String.__proto__.prototype | undefined | 
| Array | Array.__proto__.prototype | undefined | 
| Date | Date.__proto__.prototype | undefined | 
| Function | Function.__proto__.prototype | undefined | 
| temp_O | temp_O.__proto__.prototype | undefined | 
| temp_B | temp_B.__proto__.prototype | undefined | 
| temp_N | temp_N.__proto__.prototype | undefined | 
| temp_S | temp_S.__proto__.prototype | undefined | 
| temp_A | temp_A.__proto__.prototype | undefined | 
| temp_D | temp_D.__proto__.prototype | undefined | 
| temp_F | temp_F.__proto__.prototype | undefined | 
4 new
| 1 | var Person = function(){}; | 
new的过程拆分成以下三步:
- var p={};也就是说,初始化一个空对象- p。
- p.__proto__ = Person.prototype;将临时对象的- 隐式声明指向构造体的- 显式声明上。
- Person.call(p);也就是说构造- p,也可以称之为初始化- p。
关键在于第二步,我们来证明一下:
| 1 | var Person = function(){}; | 
5 instanceof
5.1 定义
在 JavaScript 中,判断一个变量的类型常常会用 typeof 运算符,在使用 typeof 运算符时采用引用类型存储值会出现一个问题,无论引用的是什么类型的对象,它都返回 object。ECMAScript 引入了 Java 中的运算符 instanceof 来解决这个问题。instanceof 运算符与 typeof 运算符相似,用于识别正在处理的对象的类型。与 typeof 方法不同的是,instanceof 方法要求开发者明确地确认对象为某特定类型。
instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。请注意这个是检测的是原型链,不是单纯的只检测对象的隐式声明一次,是不断的在对象的隐式声明中去找构造体的显式声明,注意如果传入的第一个参数如果不是对象,会直接返回false。
5.2 用法
通常来讲,使用 instanceof 就是判断一个实例是否属于某种类型。
| 1 | // 判断 foo 是否是 Foo 类的实例 | 
另外,更重的一点是 instanceof 可以在继承关系中用来判断一个实例是否属于它的父类型。
| 1 | // 判断 foo 是否是 Foo 类的实例 , 并且是否是其父类型的实例 | 
上面的代码中是判断了一层继承关系中的父类,在多层继承关系中,instanceof 运算符同样适用。
5.3 语言规范
ECMAScript-262 edition 3 中 关于 instanceof 运算符的定义
| 1 | 11.8.6 The instanceof operator | 
上面的规范定义很晦涩,而且看起来比较复杂,涉及到很多概念,但把这段规范翻译成 JavaScript 代码却很简单,
| 1 | 
 |