【Dmitri Pavlutin】JavaScript继承:理解构造函数属性

翻译:道奇
做者:Dmitri Pavlutin
原文:Inheritance in JavaScript: Understanding the constructor Propertyhtml

JavaScript有一种有趣的继承机制:prototypal(原型链),大部分刚开始JavaScript的开发人员很难理解它,我也是。bash

JavaScript中的全部类型(除了nullundefined值)都有构造函数属性,它是继承的一部分。例如:函数

var num = 150;
num.constructor === Number // => true
var obj = {};
obj.constructor === Object // => true
var reg = /\d/g;
reg.constructor === RegExp; // => true
复制代码

在这篇文章中,咱们将深刻学习对象的构造函数属性,它做为类的公共特性,意味着它能够用于:学习

  • 标识出对象属于哪一个类(instanceOf的另外选择)
  • 从对象或原型(prototype)引用构造函数
  • 获取类名

1.原始类型的构造函数

JavaScript中,原始类型指的是数字、布尔、字符串、symbol(ES6新增类型),nullundefined。除了nullundefined以外的任何值都有构造函数属性,该属性指的是对应的类型函数:ui

  • 数字的Number(): (1).constructor === Number
  • 布尔类型的Boolean(): (true).constructor === Boolean
  • 字符串的String(): ('hello').constructor === String
  • SymbolSymbol(): Symbol().constructor === Symbol

经过将它与相应的函数进行比较,可将原始类型的构造函数属性用于肯定它的类型,例如,验证值是不是数字:this

if (myVariable.constructor === Number) {
   // 当myVariable是数字时,代码才执行
   myVariable += 1;
}
复制代码

注意,这种方法通常不推荐,更推荐typeof的方式(见1.1),但这种方法在switch语句中颇有用,能够减小if/else的数量:spa

// myVariable = ...
var type;
switch (myVariable.constructor) {
  case Number:
    type = 'number';
    break;
  case Boolean:
    type = 'boolean';
    break;
  case String:
    type = 'string';
    break;
  case Symbol:
    type = 'symbol';
    break;
  default:
    type = 'unknown';
    break;
}
复制代码

1.1 原始值的包装对象

经过new运算符调用函数时,会建立一个原始值的包装对象,new String('str'),new Number(15)new Boolean(true)均可以建立一个包装对象,但Symbol是不会建立包装对象的,由于以new Symbol('symbol')这种方式调用会产生类型异常的错误。prototype

包装对象容许开发人员将自定义属性和方法绑定到原始值上,由于JavaScript不容许原始值有本身的属性。翻译

在基于构造函数判断变量的类型时,这些包装对象的存在可能会对形成理解上的混乱,由于包装对象具备与原始值相同的构造函数:code

var booleanObject = new Boolean(false);
booleanObject.constructor === Boolean // => true

var booleanPrimitive = false;
booleanPrimitive.constructor === Boolean // => true
复制代码

2.原型(prototype)对象的构造函数

原型(prototype)中的构造函数属性会自动设置为构造函数的引用。

function Cat(name) {
  this.name = name;
}
Cat.prototype.getName = function() {
  return this.name;
}
Cat.prototype.clone = function() {
  return new this.constructor(this.name);
}
Cat.prototype.constructor === Cat // => true
复制代码

由于属性是继承自原型(prototype)的,实例对象也有构造函数。

var catInstance = new Cat('Mew');
catInstance.constructor === Cat // => true
复制代码

甚至从直接量上建立的对象,也是继承自Object.prototype

var simpleObject = {
  weekDay: 'Sunday'
};
simpleObject.prototype === Object // => true
复制代码

2.1 不要在子类中丢失构造函数

构造函数是原型对象中常规的不可枚举属性,当基于它建立新的对象的时候,它不会自动更新。当建立子类时,须要手动设置正确的构造函数。

下面的例子为超类Cat建立一个子类Tiger。注意初始化时Tiger.prototype仍然指向Cat构造函数。

function Tiger(name) {
   Cat.call(this, name);
}
Tiger.prototype = Object.create(Cat.prototype);
//prototype有个不正确的构造函数
Tiger.prototype.constructor === Cat   // => true
Tiger.prototype.constructor === Tiger // => false
复制代码

如今若是使用Cat.prototype中定义的clone()方法克隆一个Tiger实例,会建立一个错误的Cat实例。

var tigerInstance = new Tiger('RrrMew');
var wrongTigerClone = tigerInstance.clone();
tigerInstance instanceof Tiger    // => true
// 注意wrongTigerClone是个不正确的Cat实例
wrongTigerClone instanceof Tiger  // => false
wrongTigerClone instanceof Cat    // => true
复制代码

会出错的缘由是Cat.prototype.clone()使用 new this.constructor()建立新的备份,但构造函数始终指向Cat函数。

为了解决这个问题,必需使用正确的构造函数:Tiger,手动更新Tiger.prototype,这样clone()方法也会被修复。

//修改Tiger原型构造函数
Tiger.prototype.constructor = Tiger;
Tiger.prototype.constructor === Tiger // => true

var tigerInstance = new Tiger('RrrMew');
var correctTigerClone = tigerInstance.clone();

//注意correctTigerClone是正确的Tiger实例
correctTigerClone instanceof Tiger  // => true
correctTigerClone instanceof Cat    // => false
复制代码

查看此demo以得到完整的示例。

instanceof的另外一个选择

object instanceof Class用于肯定对象是否和Class有一样的prototype

这个操做符也会在原型链里进行搜索,这样作有时候会使得区分子类实例和超类实例变得很困难,例如:

var tigerInstance = new Tiger('RrrMew');

tigerInstance instanceof Cat   // => true
tigerInstance instanceof Tiger // => true
复制代码

就像这个例子中看到的,不太可能准确的确认tigerInstanceCat仍是Tiger,由于instanceof在两种状况下都返回true。 这就是构造函数属性的闪光点,它容许严格肯定实例类。

tigerInstance.constructor === Cat   // => false
tigerInstance.constructor === Tiger // => true

// 或者使用switch
var type;
switch (tigerInstance.constructor) {
  case Cat:
    type = 'Cat';
    break;
  case Tiger:
    type = 'Tiger';
    break;
  default:
    type = 'unknown';    
}
type // => 'Tiger'
复制代码

获取类名

JavaScript中的函数对象有个属性名称,它返回函数名或匿名函数的空字符串。

除了构造函数属性以外,这对于肯定类名也颇有用,做为Object.prototype.toString.call(objectInstance)的另一种选择。

var reg = /\d+/;
reg.constructor.name                // => 'RegExp'
Object.prototype.toString.call(reg) // => '[object RegExp]'

var myCat = new Cat('Sweet');
myCat.constructor.name                // => 'Cat'
Object.prototype.toString.call(myCat) // => '[object Object]'
复制代码

可是name返回匿名函数的空字符串(可是在ES6中,名称能够推断出来),这种方法应该谨慎使用。

总结

构造函数属性是JavaScript的继承机制的一小部分。建立类的层级结构时应采起预防措施, 可是,它提供了肯定实例类型的很好的替代方法。

还能够看一下
Object.prototype.constructor
What’s up with the constructor property in JavaScript?

相关文章
相关标签/搜索