JavaScript原型系列(一)构造函数、原型和原型链

大方無隅,大器晚成,大音希聲,大象無形。——《道德經》javascript

JavaScript原型系列(一)构造函数、原型和原型链html

JavaScript原型系列(二)什么是原型继承java

JavaScript原型系列(三)Function、Object、Null等等的关系和鸡蛋问题浏览器

简介

首先要了解几个属性constructorprototype[[prototype]]__proto__分别做用是什么,还要理解几个概念原型原型链构造函数函数

结合代码先把上面的的属性和记录清楚。post

构造函数

constrcutor是一种用于建立和初始化class建立的对象的特殊方法。 构造函数自己就是一个函数,与普通函数没有任何区别,不过为了规范通常将其首字母大写。构造函数普通函数的区别在于,使用 new 生成实例的函数就是构造函数,直接调用的就是普通函数。下面示例代码:性能

function ConstructorFun (name) {
        this.name = name;
    }
    // 经过new关键字建立实例
    let constructorfun = new ConstructorFun();
复制代码

其实ConstructorFun就是一个普通函数,可是在经过new关键字生成实例的时候,就能够把这个函数叫作构造函数;ui

constructor

除了nullundefined其余不管是经过new生成的实例,仍是经过字面量生成的变量,普通的函数都是有constructor属性的。this

代码以下:spa

function ConstructorFun (name) {
        this.name = name;
    }
    // 经过new关键字建立实例
    var constructorfun = new ConstructorFun();
    constructorfun.constructor === ConstructorFun; // true
    var number = 111;
    console.log(number.constructor); // ƒ Number() { [native code] }
复制代码

构造函数扩展

  • let a = {} 实际上是 let a = new Object() 的语法糖
  • let a = [] 实际上是 let a = new Array() 的语法糖
  • function Foo(){ … } 实际上是 var Foo = new Function(…)
  • 可使用 instanceof 判断一个函数是否为一个变量的构造函数

手动实现一个instanceof函数以下:

// 模拟实现instanceof
    function selfInstanceof (left, right) {  //left 表示左表达式,right 表示右表达式
        let cur = left.__proto__; // 取的cur的隐式原型
        let parent = right.prototype; // 取的right的显式原型
        while(true) {
            if (cur === null) { // 若是cur为null 直接返回false
                return false;
            }
            if (cur === parent) { // 若是cur与parent相同 返回true
                return true;
            }
            cur = cur.__proto__; // 上面两个条件都不知足,继续向上一层原型链查找
        }
    }
复制代码

constructor 的值是否可更改

对于引用类型来讲constructor 属性值是能够修改的,可是对于基本类型来讲是只读的。

注意:nullundefined 是没有 constructor 属性的。

原型

官方解释原型:"JavaScript常被描述为一种基于原型的语言(prototype-based language)————每一个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。" 每一个函数都有一个特殊的属性就叫做原型(prototype),请看下面代码:

function Foo () {}
    console.log(Foo.prototype);
复制代码

效果以下图所示:

JavaScript-prototype
Foo.prototype上有两个属性,一个是 constructor它指向了函数自己;另外一个是 __proto__它指向了 Object.prototype

构造函数Foo有一个指向原型的指针,原型Foo.prototype有一个指向构造函数的指针Foo.prototype.constructor,用下面的图来表示更清晰一点:

JavaScript-prototype

其实更重要的是任何一个prototype对象都有一个constructor属性,指向这个构造函数。

proto

在上面看到__proto__这个属性,每一个实例对象(object)都有一个隐式原型属性(称之为__proto__)指向了建立该对象的构造函数的原型

function Foo (name) { this.name = name; }
    var foo = new Foo();
复制代码

效果图以下:

JavaScript-prototype
当经过 new Foo()生成的实例对象 foo,它有一个 __proto__属性指向 Foo.prototype,能够经过如下代码验证:

foo.__proto__ === Foo.prototype; // true
复制代码

FooFoo.prototypeFoo.prototype.constructorfoo.__proto__三者的关系以下图所示:

JavaScript-prototype
__proto__ 属性在 ES6 时才被标准化,以确保 Web 浏览器的兼容性,可是不推荐使用,除了标准化的缘由以外还有性能问题。为了更好的支持,推荐使用 Object.getPrototypeOf()

经过改变一个对象的 [[Prototype]] 属性来改变和继承属性会对性能形成很是严重的影响,而且性能消耗的时间也不是简单的花费在 obj.__proto__ = ... 语句上, 它还会影响到全部继承自该 [[Prototype]] 的对象,若是你关心性能,你就不该该修改一个对象的 [[Prototype]]

若是要读取或修改对象的 [[Prototype]] 属性,建议使用以下方案,可是此时设置对象的 [[Prototype]] 依旧是一个缓慢的操做,若是性能是一个问题,就要避免这种操做。

// 获取
    Object.getPrototypeOf()
    Reflect.getPrototypeOf()

    // 修改
    Object.setPrototypeOf()
    Reflect.setPrototypeOf()
复制代码

若是要建立一个新对象,同时继承另外一个对象的 [[Prototype]] ,推荐使用 Object.create()

function Parent() {
        age: 50
    };
    var p = new Parent();
    var child = Object.create(p);
复制代码

[[Prototype]]

[[Prototype]] 是对象的一个内部属性,外部代码没法直接访问。

遵循 ECMAScript 标准,someObject.[[Prototype]] 符号用于指向 someObject 的原型

原型链

每一个对象拥有一个原型对象,经过 __proto__ 指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null。这种关系被称为原型链 (prototype chain),经过原型链一个对象会拥有定义在其余对象中的属性和方法。看一面一张经典的图可能更直观:

JavaScript-prototype

看一下面的代码:

function Foo () {}
    var foo = new Foo();
    foo.__proto__ === Foo.prototype; // true
    foo.__proto__.__proto__ === Object.prototype; // true
    foo.__proto__.__proto__.__proto__ === null; // true
复制代码

下面的图能够很好的展现上面的代码prototype__proto__指向问题。

JavaScript-prototype

特殊的Symbol

Symbol是基础数据类型,它能够经过Symbol(123)生成实例,不能经过new Symbol()生成实例,Symbol不是构造函数,可是它有constructor属性。

let sSymbol = Symbol('symbol');
    let errSymbol = new Symbol('symbol'); // Uncaught TypeError: Symbol is not a constructor

    Symbol.constructor; // ƒ Symbol() { [native code] }
复制代码

总结

  • 每个函数对象都有一个prototype属性,指向函数对象的原型,原型对象上有一个constructor属性指向构造函数自己。
  • 引用类型 constructor 属性值是能够修改的,可是对于基本类型来讲是只读的,固然 nullundefined 没有 constructor 属性。
  • __proto__ 属性在 ES6 时被标准化,但由于性能问题并不推荐使用,推荐使用 Object.getPrototypeOf()
  • __proto__ 是每一个实例上都有的属性,prototype 是构造函数的属性,在实例上并不存在,因此这两个并不同,但 foo.__proto__Foo.prototype 指向同一个对象。
  • 每一个对象拥有一个原型对象,经过__proto__指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null,这就是原型链。

参考

从新认识构造函数、原型和原型链

JS 系列二:原型与原型链

对象原型

相关文章
相关标签/搜索