面向对象与原型

TMaize 于 2017-07-19 发布

创建对象

对象的创建也比较简单,一共有三种方式

  1. 通过字面量

    var Student = {
      name: 'Robot',
      height: 1.2,
      run: function() {
        console.log(this.name + ' is running...')
      }
    }
    
    Student.run()
    
  2. 通过构造函数

    先定义一个构造函数,通过 new 构造函数可以创建一个类

    function Student(name) {
      this.name = name
      this.hello = function() {
        alert('Hello, ' + this.name + '!')
      }
    }
    
    var xiaoming = new Student('小明')
    xiaoming.name
    xiaoming.hello()
    
  3. 使用 class 关键字

    新的关键字 class 从 ES6 开始正式被引入到 JavaScript 中,让定义类和继承类更简单

    class Student {
      constructor(name) {
        this.name = name
      }
      hello() {
        alert('Hello, ' + this.name + '!')
      }
    }
    
    var xiaoming = new Student('小明')
    xiaoming.name
    xiaoming.hello()
    

原型与隐式原型

prototype 称为原型

__proto__ 称为隐式原型

prototype 是构造函数的属性,而 __proto__ 是对象的属性

为了实现继承,因此对象的隐式原型指向构造函数的原型

function Person(age) {
  this.age = age || 0
}
Person.prototype.print = function() {
  console.log('age', this.age)
}
let p = new Person(18)
console.log(p.__proto__ == Person.prototype)
console.log(p.print())

你甚至可以这样做

let arr = { 0: 'zs', 1: 'ls', length: 2 }
console.log(arr)

// 强制改成Array
arr.__proto__ = Array.prototype
console.log(arr)
arr.push('we')
console.log(arr)

同时构造函数也是一个对象,他也具有__proto__属性

Person.__proto__ == Function.prototype

对于基础构造函数的__proto__属性

// 全是 ƒ () { [native code] } 都相等
console.log(Object.__proto__)
console.log(Function.__proto__)
console.log(Array.__proto__)
console.log(Number.__proto__)
console.log(Date.__proto__)

[native code].__proto__ == Object.prototype

对于基础构造函数的prototype属性

Number.prototype // Object(0)
String.prototype // Object("")
Boolean.prototype // Object(false)
Array.prototype // []
Function.prototype // function(){}
Date.prototype // new Date(NaN)

属性/方法继承

通过原型可以清晰的发现有一条链式关系

let arr = new Array(1, 2)
app.push(3)
console.log(arr.__proto__ == Array.prototype)
console.log(Array.prototype.__proto__ == Object.prototype)
console.log(Object.prototype.__proto__ == null)

这个就是原型链

arr ---> Array.prototype ---> Object.prototype ---> null

当执行 push 方法的时候会顺着原型链去查找 push 的定义

// arr.push实际调用的是Array.prototype里面定义的方法
window.Array.prototype.push.call(arr, 3)

对于两个类是否具有继承关系,看他们的原型链就行了

function Student(name) {
  this.name = name
}
// 绑定,不是继承
function GoodStudent(name) {
  this.start = 5
  Student.call(this, name)
}
// 这才是继承
function GoodStudent(name) {
  this.start = 5
  this.__proto__ = new Student(name)
}

new 的过程

有时候会很好奇,构造函数里面的 this 怎么就可以把属性绑定到对象上面呢

其实在 new 的过程中通过 call 或者 apply 的方式 this 映射到对象上,然后去调用 constructor

let p = new Person(18)

// 等价于
let p = Object.create(Person.prototype)
// p.__proto__ == Person.prototype
p.constructor.call(p, 18)
// Person.call(p,18)

instanceof

instanceof 的左值一般是一个对象,右值一般是一个构造函数,用来判断左值是否是右值的实例

var arr = [1, 2, 3]
// true
console.log(arr instanceof Array)
// true
console.log(arr instanceof Object)

它的判断依据如下

L.__proto__.[__proto__ ...] === R.prototype

所以就不难理解

// Object.__proto__     [Native Code]
// Function.prototype   [Native Code]
// Function.__proto__   [Native Code]
console.log(Object instanceof Function) // true
console.log(Function instanceof Function) // true
console.log(Function instanceof Function) // true

// Number.__proto__                   [Native Code]
// [Native Code].__proto__            Object.prototype
// [Native Code].__proto__.__proto__  null
console.log(Number instanceof Number) // false

总结

如果浏览器版本支持,最好使用 ES6 新提供的 class 关键字去定义类

如果弄不清 this 的指向,可以直接使用箭头函数

class Person extends Object {
  // 定义类属性
  name = '临时姓名'
  constructor(age) {
    super()
    // 定义类属性
    this.age = age || 0
  }
  // 静态方法
  static Hello() {
    console.log('hello')
  }
}
console.log(new Person(18))

参考

JS 中 proto 和 prototype 存在的意义是什么

为什么 Function.prototype 可以直接执行

阮一峰的网络日志