ES5
中生成实例对象的方法是通过构造函数,首先创建一个构造函数,定义另一个方法并赋值给构造函数的原型,使用new
操作符创建一个构造函数的实例,所有实例都将共享这个方法。ES6
中通过class
关键字定义类,作为对象的模板。类中的constructor
方法就是构造方法,通过new
命令生成对象实例时,自动调用该方法。this
关键字代表实例对象,定义在this
上的属性是实例属性,否则就是定义在原型对象上的属性。
class PersonClass {
//在类中定义构造函数
constructor(name) {
this.name = name
}
sayName(){
console.log(this.name)
}
}
let person = new PersonClass('siyuan')
person.sayName() //siyuan
类的内部定义的所有方法都是不可枚举的,Object.keys(obj)
返回对象自身可枚举属性组成的数组,Object.getOwnPropertyNames(obj)
返回对象自身属性名组成的数组(包括不可枚举属性但不包括Symbol
值作为名称的属性)
Object.keys(PersonClass.prototype) //[]
Object.getOwnPropertyNames(PersonClass.prototype) // ["constructor", "sayName"]
实例上的__proto__
属性指向原型对象,可以通过该属性为类添加方法。也可通过Object.getPrototypeOf(obj)
方法获取实例对象的原型。
注意点
- 严格模式
- 类声明中的所有代码将自动运行在严格模式下。
- 不存在提升
- 函数声明可以被提升,类声明与
let
声明类似,不能被提升。真正执行声明语句之前,他们会一直存在于临时死区中。
js
引擎在扫描代码发现变量声明时,会将var
声明提升至作用域顶部,将let
和const
声明放到临时性死区(Temporal dead zone: TDZ
)中,访问TDZ
中的变量会触发运行时错误,只有执行过变量声明语句后,变量才会从TDZ
中移出,然后方可正常访问。
- 不可枚举
- 自定义类型中,需要通过
Object.defineProperty()
方法指定某个方法不可枚举,类中的所有方法都不可枚举。
-
this
指向
- 类的方法内部如果含有
this
,默认指向类的实例。可以使用箭头函数绑定this
,箭头函数内部的this
总是指向定义时所在的对象,箭头函数位于构造函数内部,定义生效时是在构造函数执行的时候,此时箭头函数所在的运行环境是实例对象,this
会总是指向实例对象。
- 除用
new
关键字以外的方法调用类的构造函数会导致程序抛出错误
//等价于PersonClass
let PersonType = (function(){
"use strict"
const PersonType = function(name){
//确保通过关键字new调用函数
if(typeof new.target === "undefined") {
throw new Error("必须通过关键字new调用构造函数")
}
this.name = name
}
Object.defineProperty(PersonType.prototype, "sayName", {
value: function(){
//确保不会通过关键字new调用该方法
if(typeof new.target !== "undefined") {
throw new Error("不可使用关键字new调用该方法")
}
console.log(this.name)
},
enumerable: false,
writable: true,
configurable: true
})
return PersonType
}())
其中,new.target
属性允许检测函数或构造函数是否通过new
运算符调用。在通过new
运算符被初始化的函数或构造方法中,new.target
返回一个指向构造方法或函数的引用,在普通函数的调用中,new.target
的值是undefined
。
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。通过赋值操作添加的普通属性是可枚举的(for...in
或Object.keys
方法),属性的值可以被改变,也可以被删除。
访问器属性
类支持在原型上定义访问器属性,创建getter/setter
时,需要在关键字get/set
后紧跟一个空格和相应的标识符。
class CustomHTMLElement {
constructor (element){
this.element = element
}
get html(){
return this.element.innerHTML
}
set html(value) {
this.element.innerHTML = value
}
}
var descriptor = Object.getOwnPropertyDescriptor(CustomHTMLElement.prototype, "html")
console.log("get" in descriptor) //true
console.log("set" in descriptor) //true
其中,Object.getOwnPropertyDescriptor(obj, prop)
返回指定对象上一个自有属性对应的属性描述符(自有属性是指直接赋予该对象的属性,不需要从原型链上进行查找的属性)
静态方法
ES5
及早期版本中,直接将方法添加到构造函数中模拟静态成员,ES6
中创建静态成员,在方法或访问器属性名前使用静态注释static
即可。
类相当于实例的原型,所有在类中定义的方法都会被实例继承。在方法前加上static
关键字,表示该方法直接通过类调用,不会被实例继承,称为''静态方法''。静态方法中的this
指的是类。父类的静态方法可以被子类继承。
class Foo {
static bar() {
this.baz()
}
static baz() {
console.log('hello')
}
baz() {
console.log('world')
}
}
Foo.bar() //hello
实例属性新写法
私有属性是实例中的属性,不会出现在原型上,只能在类的构造函数或方法中创建。
实例属性除了定义在constructor()
方法里的this
上,也可以定义在类的最顶层。
class foo {
bar = 'hello'
baz = 'world'
constructor(){
//...
}
}
私有方法和私有属性
私有方法和私有属性,是只能在类的内部访问的方法和属性,外部不能访问。这是常见需求,有利于代码封装。
- 在私有方法前面加
_
,表示这是一个只限于内部使用的私有方法 - 利用
Symbol
值的唯一性,将私有方法的名字命名为一个Symbol
值。
const bar = Symbol('bar')
const snaf = Symbol('snaf')
class myClass {
//公有方法
foo(baz) {
this[bar](baz)
}
//私有方法
[bar](baz) {
return this[snaf] = baz
}
}
const inst = new myClass()
Reflect.ownKeys(myClass.prototype) //["constructor", "foo", Symbol(bar)]
bar
和snaf
都是 Symbol
值,一般情况下无法获取,因此达到了私有方法和私有属性的效果。而Reflect.ownKeys()
返回一个对象自身属性键组成的数组 ,依然可以获取。
类的继承
ES5组合继承
ES5
及早期版本借用构造函数实现对实例属性的继承,通过原型式继承实现对原型属性和方法的继承。
//ES5及早期版本
function Rectangle(length, width) {
this.length = length
this.width = width
}
Rectangle.prototype.getArea = function(){
return this.length * this.width
}
function Square(length) {
Rectangle.call(this, length, length)
}
Square.prototype = Object.create(Rectangle.prototype, {
constructor: {
value: Square,
enumerable: true,
writable: true,
configurable: true
}
})
var square = new Square(3)
console.log(square.getArea())
console.log(square instanceof Square)
console.log(square instanceof Rectangle)
类的继承
ES6
使用extends
关键字指定类继承的函数,通过调用super()
方法访问基类的构造函数。
class Rectangle {
constructor(length, width) {
this.length = length
this.width = width
}
getArea() {
return this.length * this.width
}
}
class Square extends Rectangle {
constructor(length) {
//等价于Rectangle.call(this, length, length)
super(length, length)
}
}
var square = new Square(3)
console.log(square.getArea()) //9
console.log(square instanceof Square) //true
console.log(square instanceof Rectangle) //true
super()的用法
ES5
的继承,先创造了子类实例对象的this
,将父类的方法添加到this
上(parent.apply(this)
)。ES6
的继承机制不同,现将父类实例对象的属性和方法加到this
上,所以必须先调用super()
方法,然后再用子类的构造函数修改this
。
继承自其它的类被称作派生类,如果在派生类中指定了构造函数则必须调用super()
,如果不使用构造函数,则当创建新的类的实例时会自动调用super()
并传入所有参数。
class Square extends Rectangle {
//没有构造函数
}
//等价于
class Square extends Rectangle {
constructor(...args) {
super(...args)
}
}
- 只能在
extends
声明的类中使用super()
- 在构造函数中使用
this
之前一定要调用super()
,它负责初始化this
。子类的this
对象,必须通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后对其加工,加上子类自己的实例属性和方法。 - 如果基类有静态成员,那么静态成员在派生类中也可用。
- 只要表达式可以被解析为一个函数并且具有
[[construct]]
属性和原型,就可以使用extends
进行派生
super
作为函数调用时,只能用于子类的构造函数中,代表父类的构造函数。在子类的普通方法中通过super
调用父类的方法时,方法内部的this
指向当前的子类实例;在子类的静态方法中通过super
调用父类的方法时,方法内部的this
指向当前的子类。
class A {
constructor() {
this.x = 1
console.log(new.target.name)
}
print() {
console.log(this.x)
}
static prints(){
console.log(this.x)
}
}
class B extends A {
constructor() {
super()
this.x = 2
//super虽然表示父类的构造函数,返回的是子类B的实例,即super()内部的this指向的是B,super()在这里相当于A.prototype.constructor.call(this)
}
m () {
super.print()
}
static n() {
super.prints()
}
}
let a = new A() //A
let b = new B() //B
b.m() //2
B.x = 3
B.n() // 3
>```
> - `super`作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
> ```javascript
class A {
constructor(){
this.p = 2
}
q() {
return 3
}
}
class B extends A {
constructor(){
super()
}
get m(){
console.log(super.p)
console.log(super.q())
}
}
let b = new B()
b.m // 3 undefined
//这里super指向父类的原型对象,定义在父类实例上的方法或属性无法通过super调用
内建对象的继承
-
ES5
的继承中,先由子类创建this
的值,然后调用父类的构造函数,如Array.apply()
方法,即this
开始指向的是子类的实例,然后被来自父类的其它属性修饰,无法实现内建对象的继承。 -
ES6
中的继承,由父类创建this
的值 ,然后子类的构造函数修改这个值,因此初始可以通过this
访问基类的所有内建功能 ,然后正确接收所有与之相关的功能
Symbol.species
用于定义返回函数的静态访问器属性,被返回的函数是一个构造函数,当要在实例的方法中创建类的实例时必须使用这个构造函数。它被构造函数用于创建派生对象,允许子类覆盖对象的默认构造函数。
class Array1 extends Array {
static get [Symbol.species]() {
return Array
}
}
const arr = new Array1(1, 2, 3)
const mapped = arr.map(x => x * x)
console.log(mapped instanceof Array1)//false
console.log(mapped instanceof Array) //true
网友评论