《JavaScript面向对象精要》之四:构造函数和原型对象

这里有一份简洁的前端知识体系等待你查收,看看吧,会有惊喜哦~若是以为不错,恳求star哈~前端


因为 JavaScript(ES5) 缺少类,但可用构造函数和原型对象给对象带来与类类似的功能。git

4.1 构造函数

构造函数的函数名首字母应大写,以此区分其余函数。github

当没有须要给构造函数传递参数,可忽略小括号:api

var Person = {
  // 故意留空
}
var person = new Person;
复制代码

尽管 Person 构造函数没有显式返回任何东西,但 new 操做符会自动建立给定类型的对象并返回它们。数组

每一个对象在建立时都自动拥有一个构造函数属性(constructor,实际上是它们的原型对象上的属性),其中包含了一个指向其构造函数的引用。bash

经过对象字面量形式({})或 Object 构造函数建立出来的泛用对象,其构造函数属性(constructor)指向 Object;而那些经过自定义构造函数建立出来的对象,其构造函数属性指向建立它的构造函数。函数

console.log(person.constructor === Person); // true
console.log(({}).constructor === Object); // true
console.log(([1,2,3]).constructor === Object); // false

// 证实 constructor 是在原型对象上
console.log(person.hasOwnProperty("constructor")); // false
console.log(person.constructor.prototype.hasOwnProperty("constructor")); // true
复制代码

尽管对象实例及其构造函数之间存在这样的关系,但仍是建议使用 instanceof 来检查对象类型。这是由于构造函数属性能够被覆盖。(person.constructor = "")。ui

当你调用构造函数时,new 会自动自动建立 this 对象,且其类型就是构造函数的类型(构造函数就像类,至关于一种数据类型)。this

你也能够在构造函数中显式调用 return。若是返回值是一个对象,它会代替新建立的对象实例而返回,若是返回值是一个原始类型,它会被忽略,新建立的对象实例会被返回。spa

始终确保要用 new 调用构造函数;不然,你就是在冒着改变全局对象的风险,而不是建立一个新的对象。

var person = Person("Nicholas"); // 缺乏 new

console.log(person instanceof Person); // false
console.log(person); // undefined,由于没用 new,就至关于一个普通函数,默认返回 undefined
console.log(name); // "Nicholas"
复制代码

当 Person 不是被 new 调用时,构造函数中的 this 对象等于全局 this 对象。

在严格模式下,会报错。由于严格模式下,并无为全局对象设置 this,this 保持为 undefined。

如下代码,经过 new 实例化 100 个对象,则会有 100 个函数作相同的事。所以可用 prototype 共享同一个方法会更高效。

var person = {
  name: "Nicholas",
  sayName: function(){
    console.log(this.name);
  }
}
复制代码

4.2 原型对象

能够把原型对象看做是对象的基类。几乎全部的函数(除了一些内建函数)都有一个名为 prototype 的属性,该属性是一个原型对象用来建立新的对象实例。

全部建立的对象实例(同一构造函数,固然,可能访问上层的原型对象)共享该原型对象,且这些对象实例能够访问原型对象的属性。例如,hasOwnProperty() 定义在 Object 的原型对象中,但却可被任何对象看成本身的属性访问。

var book = {
  title : "book_name"
}

"hasOwnProperty" in book; // true
book.hasOwnProperty("hasOwnProperty"); // false
Object.property.hasOwnProperty("hasOwnProperty"); // true
复制代码

鉴别一个原型属性

function hasPrototypeProperty(object, name){
  return name in object && !object.hasOwnProperty(name);
}
复制代码

4.2.1 [[Prototype]] 属性

一个对象实例经过内部属性 [[Prototype]] 跟踪其原型对象。

该属性是一个指向该实例使用的原型对象的指针。当你用 new 建立一个新的对象时,构造函数的原型对象就会被赋给该对象的 [[Prototype]] 属性。

Object.getPrototypeOf() 方法可读取 [[Prototype]] 属性的值。

var obj = {};
var prototype = Object.getPrototypeOf(obj);

console.log(prototype === Object.prototype); // true
复制代码

大部分 JavaScript 引擎在全部对象上都支持一个名为 __proto__ 的属性。该属性使你能够直接读写 [[Prototype]] 属性。

isPrototypeOf() 方法会检查某个对象是不是另外一个对象的原型对象,该方法包含在全部对象中。

var obj = {}
console.log(Object.prototype.isPrototypeOf(obj)); // true
复制代码

当读取一个对象的属性时,JavaScript 引擎首先在该对象的自有属性查找属性名。若是找到则返回。不然会搜索 [[Prototype]] 中的对象,找到则返回,找不到则返回 undefined。

var obj = new Object();
console.log(obj.toString()); // "[object Object]"

obj.toString = function(){
  return "[object Custom]";
}
console.log(obj.toString()); // "[object Custom]"

delete obj.toString; // true
console.log(obj.toString()); // "[object Object]"

delete obj.toString; // 无效,delete不能删除一个对象从原型继承而来的属性
cconsole.log(obj.toString()); // // "[object Object]"
复制代码

delete 操做符不能删除的属性有:①显式声明的全局变量不能被删除,该属性不可配置(not configurable); ②内置对象的内置属性不能被删除; ③不能删除一个对象从原型继承而来的属性(不过你能够从原型上直接删掉它)。

一个重要概念:没法给一个对象的原型属性赋值。但咱们能够经过 obj.constructor.prototype.sayHi = function(){console.log("Hi!")} 向原型对象添加属性。

(图片中间能够看出,为对象 obj 添加的 toString 属性代替了原型属性)

4.2.2 在构造函数中使用原型对象

原型对象的共享机制使得它们成为一次性为全部对象定义全部方法的理想手段,由于一个方法对全部的对象实例作相同的事,没理由每一个实例都要有一份本身的方法。

将方法放在原型对象中并使用this方法当前实例是更有效的作法。

function Person(name) {this.name = name}
Person.prototype.sayName = function() {console.log(this.name)};
var person1 = new Person("Nicholas")
console.log(person1.name)                        // Nicholas
person1.sayName()                                // Nicholas
复制代码

也能够在原型对象上存储其余类型的数据,可是在存储引用值时要注意,由于这些引用值会被多个实例共享,可能你们不但愿一个实例可以改变另外一个实例的值。

function Person(name) {this.name = name}
Person.prototype.favorites = []
var person1 = new Person("Nicholas")
var person2 = new Person("Greg")
person1.favorites.push("pizza")
person2.favorites.push("quinoa")

console.log(person1.favorites)                // ["pizza", "quinoa"]
console.log(person2.favorites)                // ["pizza", "quinoa"]
复制代码

favorites属性被定义到原型对象上,意味着person1.favorites和person2.favorites指向同一个数组,你对任意Person对象的favorites插入的值都将成为原型对象上数组的元素。也可使用字面量的形式替换原型对象:

function Person(name) {this.name=name}
Person.prototype= {
    sayName: function() {console.log(this.name)},
    toString: function(){return `[Person ${this.name} ]`}
}
复制代码

虽然用这种字面量的形式定义原型很是简洁,可是有个反作用须要注意。

var person1 = new Person('Nicholas')
console.log(person1 instanceof Person)                // true
console.log(person1.constructor === Person)                // false
console.log(person1.constructor === Object)                // true
复制代码

使用字面量形式改写原型对象改写了构造函数的属性,所以如今指向Object而不是Person,这是由于原型对象具备个constructor属性,这是其余对象实例所没有的。当一个函数被建立时,其prototype属性也被建立,且该原型对象的constructor属性指向该函数本身,当使用字面量形式改写原型对象Person.prototype时,其constructor属性将被复写为泛用对象Object。为了不这一点,须要在改写原型对象时手动重置其constructor属性:

function Person(name) {this.name = name}
Person.prototype = {
    constructor: Person,             // 为了避免忘记赋值,最好在第一个属性就把constructor重置为本身
    sayName() {console.log(this.name)},
    toString() {return `[Person ${this.name} ]`}
}

var person1 = new Person('Nicholas')
console.log(person1 instanceof Person)                    // true
console.log(person1.constructor === Person)                // true
console.log(person1.constructor === Object)                // false
复制代码

构造函数、原型对象、对象实例之间:对象实例和构造函数之间没有直接联系。不过对象实例和原型对象之间以及原型对象和构造函数之间都有直接联系。

这样的链接关系也意味着,若是打断对象实例和原型对象之间的联系,那么也将打断对象实例及其构造函数之间的关系。

4.2.3 改变原型对象

由于每一个对象的 [[Prototype]] 只是一个指向原型对象的指针,因此原型对象的改动会马上反映到全部引用它的对象。

当对一个对象使用封印 Object.seal() 或冻结 Object.freeze() 时,彻底是在操做对象的自有属性,但任然能够经过在原型对象上添加属性来扩展这些对象实例。

4.2.4 内建对象(如Array、String)的原型对象

全部内建对象都有构造函数,所以也都有原型对象能够去改变,例如要在数组上添加一个新的方法只须要改变Array.prototype便可

Array.prototype.sum = function() {
    return this.reduce((privious, current) => privious + current)
}
var numbers = [1, 2, 3, 4, 5, 6]
var result = numbers.sum()
console.log(result)                    // 21
复制代码

sum()函数内部,在调用时this指向数组的对象实例numbers,所以this也能够调用该数组的其余方法,好比reduce()。 改变原始封装类型的原型对象,就能够给这些原始值添加更多功能,好比:

String.prototype.capitalize = function() {
    return this.charAt(0).toUpperCase() + this.substring(1)
}
var message = 'hello world!'
console.log(message.capitalize())            // Hello world!
复制代码

总结

  • 构造函数就是用 new 操做符调用的普通函数。可用过 instanceof 操做符或直接访问 constructor(其实是原型对象的属性) 来鉴别对象是被哪一个构造函数所建立的。
  • 每一个函数都有一个 prototype 对象,它定义了该构造函数建立的全部对象共享的属性。而 constructor 属性其实是定义在原型对象里,供全部对象实例共享。
  • 每一个对象实例都有 [[Prototype]] 属性,它是指向原型对象的指针。当访问对象的某个属性时,先从对象自身查找,找不到的话就到原型对象上找。
  • 内建对象的原型对象也可被修改
相关文章
相关标签/搜索