构造函数与 new 命令

1. 对象是什么

  • 面向对象编程(Object Oriented Programming,缩写为 OOP)
  • “对象”(object)到底是什么
    • 对象是单个实物的抽象
    • 对象是一个容器,封装了属性(property)和方法(method)

2. 构造函数

  • 典型的面向对象编程语言(比如 C++ 和 Java),都有“类”(class)这个概念。所谓“类”就是对象的模板,对象就是“类”的实例。但是,JavaScript 语言的对象体系,不是基于“类”的,而是基于构造函数(constructor)和原型链(prototype)。
  • JavaScript 语言使用构造函数(constructor)作为对象的模板。所谓”构造函数”,就是专门用来生成实例对象的函数。它就是对象的模板,描述实例对象的基本结构。一个构造函数,可以生成多个实例对象,这些实例对象都有相同的结构。
  • 构造函数就是一个普通的函数,为了与普通函数区别,构造函数名字的第一个字母通常大写

3. new 命令

  • 基本用法
    • new 命令的作用,就是执行构造函数,返回一个实例对象
    • 使用 new 命令时,根据需要,构造函数也可以接受参数
    • new 命令本身就可以执行构造函数,所以后面的构造函数可以带括号,也可以不带括号。但是为了表示这里是函数调用,推荐使用括号
    • 如果忘了使用 new 命令,直接调用构造函数,这种情况下,构造函数就变成了普通函数,并不会生成实例对象。
    • 为了保证构造函数必须与 new 命令一起使用
      1. 构造函数内部使用严格模式,即第一行加上 use strict。这样的话,一旦忘了使用 new 命令,直接调用构造函数,由于严格模式中,函数内部的 this 不能指向全局对象,默认等于 undefined,导致不加 new 调用会报错(JavaScript 不允许对 undefined 添加属性)。
      2. 构造函数内部判断是否使用 new 命令,如果发现没有使用,则直接返回一个实例对象
  • new 命令的原理

    • 使用 new 命令时,它后面的函数依次执行下面的步骤。
      1. 创建一个空对象,作为将要返回的对象实例。
      2. 将这个空对象的原型,指向构造函数的 prototype 属性,将这个空对象赋值给函数内部的 this 关键字。
      3. 开始执行构造函数内部的代码。
      4. return 对象
    • 如果构造函数内部有 return 语句,而且 return 后面跟着一个对象,new 命令会返回 return 语句指定的对象;否则,就会不管 return 语句,返回 this 对象
    • 如果对普通函数(内部没有 this 关键字的函数)使用 new 命令,则会返回一个空对象。
    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      function _new (constructor, params) {
      // 将 arguments 对象转为数组
      var args = [].slice.call(arguments);
      // 取出构造函数
      var constructor = args.shift();
      // 创建一个空对象,继承构造函数的 prototype 属性
      var context = Object.create(constructor.prototype);
      // 执行构造函数
      var result = constructor.apply(context, args);
      // 如果返回结果是对象,就直接返回,否则返回 context 对象
      return (typeof result === 'object' && result != null) ? result : context;
      }

      // 实例
      var actor = _new(Person, '张三', 28);
    • new.target

      • 函数内部可以使用 new.target 属性。如果当前函数是 new 命令调用,new.target 指向当前函数,否则为 undefined
      • 使用这个属性,可以判断函数调用的时候,是否使用 new 命令。

4. Object.create() 创建实例对象

  • 以一个现有的对象作为模板,生成新的实例对象

绑定 this 的方法

  • Function.prototype.call()
    • call 方法的参数,应该是一个对象。如果参数为空、null 和 undefined,则默认传入全局对象
    • 如果 call 方法的参数是一个原始值,那么这个原始值会自动转成对应的包装对象,然后传入 call 方法
    • call 方法还可以接受多个参数,第一个参数就是 this 所要指向的那个对象,后面的参数则是函数调用时所需的参数
  • Function.prototype.apply()
    • apply 方法的作用与 call 方法类似,也是改变 this 指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数
  • Function.prototype.bind()
    • bind 方法用于将函数体内的 this 绑定到某个对象,然后返回一个新函数
    • bind 还可以接受更多的参数,将这些参数绑定原函数的参数。
    • 如果 bind 方法的第一个参数是 null 或 undefined,等于将 this 绑定到全局对象,函数运行时 this 指向顶层对象(浏览器为 window)。
    • bind 方法每运行一次,就返回一个新函数,这会产生一些问题

prototype 对象

1. 原型对象概述

  • 构造函数的缺点
    • 通过构造函数为实例对象定义属性,虽然很方便,但是同一个构造函数的多个实例之间,无法共享属性,从而造成对系统资源的浪费。
  • prototype 属性的作用
    • JavaScript 继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享。也就是说,如果属性和方法定义在原型上,那么所有实例对象就能共享,不仅节省了内存,还体现了实例对象之间的联系
    • JavaScript 规定,每个函数都有一个 prototype 属性,指向一个对象, 对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型
    • 原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻会体现在所有实例对象上。
    • 当实例对象本身没有某个属性或方法的时候,它会到原型对象去寻找该属性或方法。如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法
    • 原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象
  • 原型链
    • JavaScript 规定,所有对象都有自己的原型对象(prototype)。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型……
    • 如果一层层地上溯,所有对象的原型最终都可以上溯到 Object.prototype,即 Object 构造函数的 prototype 属性。也就是说,所有对象都继承了 Object.prototype 的属性。这就是所有对象都有 valueOf 和 toString 方法的原因,因为这是从 Object.prototype 继承的。
    • Object.prototype 的原型是 null。null 没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是 null。
    • 读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的 Object.prototype 还是找不到,则返回 undefined。如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding)。
  • constructor 属性
    • prototype 对象有一个 constructor 属性,默认指向 prototype 对象所在的构造函数。
    • constructor 属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的
    • constructor 属性表示原型对象与构造函数之间的关联关系,如果修改了原型对象,一般会同时修改 constructor 属性,防止引用的时候出错

      2. instanceof 运算符

    • instanceof 运算符返回一个布尔值,表示对象是否为某个构造函数的实例。
    • instanceof 运算符的左边是实例对象,右边是构造函数。它会检查右边构建函数的原型对象(prototype),是否在左边对象的原型链上
    • 由于 instanceof 检查整个原型链,因此同一个实例对象,可能会对多个构造函数都返回 true
    • instanceof 运算符的一个用处,是判断对象的类型
    • 利用 instanceof 运算符,还可以巧妙地解决,调用构造函数时,忘了加 new 命令的问题(无 new 调用)
      1
      2
      3
      4
      5
      6
      7
      8
      function Fubar (foo, bar) {
      if (this instanceof Fubar) {
      this._foo = foo;
      this._bar = bar;
      } else {
      return new Fubar(foo, bar);
      }
      }

Object 对象的相关方法

  • Object.getPrototypeOf()
    • 返回参数对象的原型
  • Object.setPrototypeOf()
    • 为参数对象设置原型,返回该参数对象。它接受两个参数,第一个是现有对象,第二个是原型对象。
  • Object.create()

    • 该方法接受一个对象作为参数,然后以它为原型,返回一个实例对象。该实例完全继承原型对象的属性。
    • 1
      2
      3
      4
      5
      6
      7
      if (typeof Object.create !== 'function') {
      Object.create = function (obj) {
      function F() {}
      F.prototype = obj;
      return new F();
      };
      }
    • 如果想要生成一个不继承任何属性(比如没有toString和valueOf方法)的对象,可以将Object.create的参数设为null。

    • 使用Object.create方法的时候,必须提供对象原型,即参数不能为空,或者不是对象,否则会报错。
    • object.create方法生成的新对象,动态继承了原型。在原型上添加或修改任何方法,会立刻反映在新对象之上。
  • Object.prototype.isPrototypeOf()
    • 实例对象的isPrototypeOf方法,用来判断该对象是否为参数对象的原型。
  • Object.prototype.proto
    • 实例对象的proto属性(前后各两个下划线),返回该对象的原型。该属性可读写。
  • 获取原型对象方法的比较
    • obj.proto
      • 只有浏览器才需要部署,其他环境可以不部署
    • obj.constructor.prototype
      • 在手动改变原型对象时,可能会失效, 一般要同时设置constructor属性
    • Object.getPrototypeOf(obj)
  • Object.getOwnPropertyNames()
    • 返回一个数组,成员是参数对象本身的所有属性的键名,不包含继承的属性键名, 不管键名是否可以遍历
  • Object.prototype.hasOwnProperty()
    • 对象实例的hasOwnProperty方法返回一个布尔值,用于判断某个属性定义在对象自身,还是定义在原型链上
  • in 运算符和 for…in 循环
    • in运算符返回一个布尔值,表示一个对象是否具有某个属性。它不区分该属性是对象自身的属性,还是继承的属性
    • 获得对象的所有可遍历属性(不管是自身的还是继承的),可以使用for…in循环
    • 为了在for…in循环中获得对象自身的属性,可以采用hasOwnProperty方法判断一下。
  • 对象的拷贝
    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      function copyObject(orig) {
      var copy = Object.create(Object.getPrototypeOf(orig));
      copyOwnPropertiesFrom(copy, orig);
      return copy;
      }

      function copyOwnPropertiesFrom(target, source) {
      Object
      .getOwnPropertyNames(source)
      .forEach(function (propKey) {
      var desc = Object.getOwnPropertyDescriptor(source, propKey);
      Object.defineProperty(target, propKey, desc);
      });
      return target;
      }

面向对象编程的模式

1. 构造函数的继承