转行学前端的第 45 天 : 了解 ECMAScript 显式原型与隐式原型

1,272 阅读9分钟

我是小又又,住在武汉,做了两年新媒体,准备用 6 个月时间转行前端。

今日学习目标

昨天基于搜索基础学习了js类型转换,然后今天就是准备学习一下显式原型隐式原型,同时了解一下new关键词构造对象的逻辑,然后详细学习一下instanceof,又是适合学习的一天,加油,小又又!!!!


概念

显式原型

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

这个属性是一个指针,指向一个对象,它是显示修改对象的原型的属性。

指针?


隐式原型

__proto__ --- 隐式原型 (implicit prototype link):JS中任意对象都有一个内置属性[[prototype]],是JS内部使用寻找原型链的属性在ES5之前没有标准的方法访问这个内置属性,但是大多数浏览器都支持通过__proto__来访问。

ES5中有了对于这个内置属性标准的Get方法Object.getPrototypeOf(),即Object.getPrototypeOf(obj)===obj.__proto__,用chromeFF都可以访问到对象的__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

var ss = Object.create(Boolean.prototype)

console.log(typeof ss);//object
console.log(ss.prototype);//undefined
// console.log(ss.prototype.__proto__);//Cannot read property '__proto__' of undefined
console.log(ss.__proto__);//[Boolean: false]
console.log(ss.__proto__.prototype);//undefined
console.log(ss.constructor);//[Function: Boolean]

作用

显式原型

用来实现基于原型的继承与属性的共享。

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

ECMAScript没有如C++SimalTalkjava中的类。

相反,可以通过各种方式创建对象,包括通过文字表示法或通过构造函数创建对象,然后通过为其属性分配初始值来执行初始化全部或部分对象的代码。

每个构造函数都是一个具有名为prototype的属性的函数,该属性用于实现基于prototype的继承和共享属性。

对象通过在新表达式中使用构造函数创建;例如,new Date(2009,11)创建新的日期对象。

----ECMAScript语言规范

偷懒用的在线翻译的~~~~


隐式原型

构成原型链,同样用于实现基于原型的继承。

举个例子,当我们访问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

构造函数创建的每个对象都有对其构造函数prototype值的隐式引用(称为对象的prototype


联合案例

//新建测试变量
var temp_O = new Object();
var temp_B = new Boolean();
var temp_N = new Number();
var temp_S = new String();
var temp_A = new Array();
var temp_D = new Date();
var temp_F = new Function();

显式声明的隐式声明

除了undefinednull对象类型之外,对象的隐式声明(__proto__)都是{},因为内建对象(built-in object)的显式声明和实例对象的显式声明都是一个对象。比如Array.prototypetemp_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__ {}

隐式声明的显式声明

除了undefinednull对象类型之外,所有对象的显式声明(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

new

var Person = function(){};
var p = new Person();

new的过程拆分成以下三步:

  • var p={}; 也就是说,初始化一个空对象p
  • p.__proto__ = Person.prototype; 将临时对象的隐式声明指向构造体的显式声明上。
  • Person.call(p); 也就是说构造p,也可以称之为初始化p

关键在于第二步,我们来证明一下:

var Person = function(){};
var p = new Person();
console.log(p.__proto__ === Person.prototype);//true

instanceof

定义

JavaScript 中,判断一个变量的类型常常会用 typeof 运算符,在使用 typeof 运算符时采用引用类型存储值会出现一个问题,无论引用的是什么类型的对象,它都返回 objectECMAScript 引入了 Java 中的运算符 instanceof 来解决这个问题。

instanceof 运算符与 typeof 运算符相似,用于识别正在处理的对象的类型。与 typeof 方法不同的是,instanceof 方法要求开发者明确地确认对象为某特定类型。

instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。

请注意这个是检测的是原型链,不是单纯的只检测对象的隐式声明一次,是不断的在对象的隐式声明中去找构造体的显式声明

注意如果传入的第一个参数如果不是对象,会直接返回false


用法

通常来讲,使用 instanceof 就是判断一个实例是否属于某种类型。

// 判断 foo 是否是 Foo 类的实例
function Foo(){}
var foo = new Foo();
console.log(foo instanceof Foo)//true

另外,更重的一点是 instanceof 可以在继承关系中用来判断一个实例是否属于它的父类型。

// 判断 foo 是否是 Foo 类的实例 , 并且是否是其父类型的实例
function Aoo(){}
function Foo(){}
Foo.prototype = new Aoo();//JavaScript 原型继承

var foo = new Foo();
console.log(foo instanceof Foo)//true
console.log(foo instanceof Aoo)//true

上面的代码中是判断了一层继承关系中的父类,在多层继承关系中,instanceof 运算符同样适用。


语言规范

  • ECMAScript-262 edition 3 中 关于 instanceof 运算符的定义

11.8.6 The instanceof operator The production RelationalExpression: RelationalExpression instanceof ShiftExpression is evaluated as follows:

1. Evaluate RelationalExpression.

2. Call GetValue(Result(1)).// 调用 GetValue 方法得到 Result(1) 的值,设为 Result(2)

3. Evaluate ShiftExpression.

4. Call GetValue(Result(3)).// 同理,这里设为 Result(4)

5. If Result(4) is not an object, throw a TypeError exception.// 如果 Result(4) 不是 object,//抛出异常 /* 如果 Result(4) 没有 [[HasInstance]] 方法,抛出异常。规范中的所有 [[...]] 方法或者属性都是内部的, 在 JavaScript 中不能直接使用。并且规范中说明,只有 Function 对象实现了 [[HasInstance]] 方法。 所以这里可以简单的理解为:如果 Result(4) 不是 Function 对象,抛出异常 */

6. If Result(4) does not have a [[HasInstance]] method, throw a TypeError exception. // 相当于这样调用:Result(4).[[HasInstance]](Result(2))

Call the [[HasInstance]] method of Result(4) with parameter Result(2).

8. Return Result(7).


  • 相关的 HasInstance 方法定义

15.3.5.3 [[HasInstance]] (V) Assume F is a Function object.// 这里 F 就是上面的 Result(4),V 是 Result(2)

When the [[HasInstance]] method of F is called with value V, the following steps are taken:

1. If V is not an object, return false.// 如果 V 不是 object,直接返回 false

2. Call the [[Get]] method of F with property name "prototype".// 用 [[Get]] 方法取 // F 的 prototype 属性

3. Let O be Result(2).//O = F.[[Get]]("prototype")

4. If O is not an object, throw a TypeError exception.

5. Let V be the value of the [[Prototype]] property of V.//V = V.[[Prototype]]

6. If V is null, return false. // 这里是关键,如果 O 和 V 引用的是同一个对象,则返回 true;否则,到 Step 8 返回 Step 5 继续循环

7. If O and V refer to the same object or if they refer to objects joined to each other (section 13.1.2), return true.

8. Go to step 5.


  • 模拟instanceof功能

上面的规范定义很晦涩,而且看起来比较复杂,涉及到很多概念,但把这段规范翻译成 JavaScript 代码却很简单,


// object instanceof constructor

//instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上。

//模拟instanceof 功能
function instance_of(L, R) {//L 表示左表达式,R 表示右表达式
 var O = R.prototype;// 取 R 的显示原型
 L = L.__proto__;// 取 L 的隐式原型
 //请注意这个判断条件,目的是无限循环,不断在L的隐式声明链中找R的显式说明,只有完全找不到的时候,才会返回false,
 //请注意undefined,null没有属性的,没有隐式声明的。
 while (true) {
   if (L === null){
    return false;
   }
   if (O === L)// 这里重点:当 O 严格等于 L 时,返回 true
   {
    return true;
   }
   L = L.__proto__;//L.__proto__.__proto__.__proto__.__proto__.__proto__..........
 }
}

其实看着还是有的没完全理解~~~~


参考网站


今日学习总结


今日心情

今日主要是基于搜索来基础学习显式原型隐式原型newinstanceof,印象比较深的是除了undefinednull对象类型之外,对象的隐式声明(__proto__)都是{},显式声明(prototype)都是undefined,感觉还是需要在实践的时候,再深入理解一下,希望明天学到更多的内容~~~~

本文使用 mdnice 排版