本文主要内容:
- 简单介绍一下什么是原型(链)
- 通过例子探究一下 JS 的类,以及类实例化的过程和函数的声明过程
应该还能了解到:
[[prototype]]、__proto__和prototype的区别
原型(链)
JavaScript中的所有对象都有一个内置的属性,称为原型。这个内置属性在ES6之前是__proto__,__proto__是一个非标准的属性,实际上是由各个浏览器厂商添加的。在ES6中,官方引入了标准的属性 [[prototype]],但是为了向下兼容,__proto__ 仍然保留。
同时,原型也是一个对象,也有一个原型属性,这样就会形成原型链,原型链终止于拥有 null 作为其原型的对象上。当我们访问对象的某个属性时,如果对象本身找不到这个属性,则会在原型中搜索,如果还是没有,就会继续搜索原型的原型,依次类推,直到找到该属性,或者到达原型链末端,这时则返回 undefined。
打印一下如下的字面量对象:
console.log({
name: 'Nio',
})
我们可以看到:
这个对象(这里叫它A对象)的原型链是这样的:
JS 中的类
在面向对象编程中,类是一种抽象数据类型,是用来描述具有相同特征和行为的一组对象的模板或蓝图。它是由一组属性和方法组成,可以用来创建新的对象。
对于使用过基于类的语言 (如 Java 或 C++) 的开发者们来说,JavaScript 实在是有些令人困惑 —— JavaScript 是动态的,本身不提供一个 class 的实现。即便是在 ES2015/ES6 中引入了 class 关键字,但那也只是语法糖,JavaScript 仍然是基于原型的。
在 Java 中,一旦定义了一个类,编译后会生成一个 class 文件。并且,这个类是静态的,可以在程序的任何地方实例化它,而且一旦实例化,对象的属性和方法就不能再改变(正常情况下)。
JavaScript 中并没有这样的类,它是基于原型的,每个对象都有一个指向它的原型对象的引用,在原型对象中定义的属性和方法可以被所有该对象的实例共享。并且,JavaScript 的类是动态的(类也是一个对象),可以在程序运行时修改类的属性和方法,甚至可以动态修改对象的原型,因为它的原型仅是一个属性(/对象引用)。
ES6 中的 class 定义的类其实就是一个方法(或者说函数,本文不明确区分两者概念),方法在 JS 是一个 Function 对象。
可以在代码中试一下:
class Person {
constructor(name) {
this.name = name
}
sayHello() {
console.log(`${this.name} say hello~`)
}
}
const zs = new Person('zs')
console.log(typeof Person) // function
console.log(zs)
console.log(typeof zs) // object
console.log(Person.prototype === zs.__proto__) // true
console.log(Person.prototype.constructor === Person) // true
打印的结果:
所以,可以确定 JS 中的类就是一个方法。
下面一个例子是我在 webpack 中使用 ts 写的:
export class Person {
private name: string
constructor(name: string) {
this.name = name
}
sayHello() {
console.log(`${this.name} say hello~`)
}
}
在 Chrome 中查看:
同时打印一下 Person 对象和一个 Person 类:
所以,可以确定 JS 中的类就是一个方法。
并且可以画一个示意图理清其中的关系:
Person类其实是一个Function对象,这个对象包含一个prototype属性prototype对象包含定义在Person类中的sayHello方法- 和一个
constructor属性,指向的是这个Person类 - 当我们通过
new实例化(详情见下面)一个Person对象(例如:张三)的时候,这个对象的原型属性(__proto__)指向Person的prototype对象。
new 运算符
new(详情) 运算符用于创建一个新的对象实例。当我们使用 new 操作符创建一个对象时,它会进行以下几个步骤:
- 创建一个空的简单
JavaScript对象(即 {}); - 为步骤 1 新创建的对象添加属性
__proto__,将该属性链接至构造函数的原型对象; - 将步骤 1 新创建的对象作为 this 的上下文;
- 如果该函数没有返回对象,则返回 this。
函数声明过程
我们知道函数其实也是对象,那么函数声明时引擎都做了什么?
创建函数时,JavaScript 引擎会创建一个函数对象,该对象具有以下属性:
[[Prototype]]指向Function.prototypelength属性表示函数的形参个数name属性表示函数的名称prototype属性指向该函数的原型对象[[Call]]内部方法用于调用该函数
函数声明或者说函数对象实例化(不特指new),这个过程和类(即函数对象)实例化对象时是类似的,都会有原型属性指向类对象的prototype(js 的类是基于原型的,继承是通过原型链)。
总结
JS对象都存在一个原型属性,这个属性也是一个对象,也有一个原型属性,这些指向形成了原型链。- 这个原型属性在
ES6中是[[prototype]],但是为了向下兼容,__proto__仍然保留。 JS的类是函数,JS的函数是一个Function对象,包含prototype属性(当然,还有其他属性),这个prototype有一个constructor属性指向类(函数)。- 类实例化对象时,这个对象的原型属性指向类的
prototype属性。