ES6学习笔记篇三之类和符号

一、类

在js语言中,生成实例对象的传统方法是通过构造函数。例如:

function Animal(type,name,age,sex){
    this.type = type
    this.name = name
    this.age = age
    this.sex = sex
}
Animal.prototype.print = function(){
     console.log(`【种类】:${this.type}`)
     console.log(`【名字】:${this.name}`)
     console.log(`【年龄】:${this.age}`)
     console.log(`【性别】:${this.sex}`)
}
const dog = new Animal("狗","巴顿",5,"公")
dog.print()
for (const key in dog) {
    console.log(key)
}

为了更好的,更简洁的书写类。ES6引入了Class类。通过class关键字来定义。ES6的类只是构造函数的语法糖

1、类的简介

通过类改造上面代码

class Animal{
    constructor(type,name,age,sex){
        this.type = type
        this.name = name
        this.age = age
        this.sex = sex
        //constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。
    }
    print(){
        console.log(`【种类】:${this.type}`)
        console.log(`【名字】:${this.name}`)
        console.log(`【年龄】:${this.age}`)
        console.log(`【性别】:${this.sex}`)
    }
}
const dog = new Animal("狗","巴顿",5,"公")
dog.print()
for (const key in dog) {
    console.log(key)
}

(1)constructor 方法

constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor`方法会被默认添加。

class Animal {
}

// 等同于
class Animal {
  constructor() {}
}

(2)取值函数(getter)和存值函数(setter)

与 ES5 一样,在“类”的内部可以使用getset关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。存值函数和取值函数是设置在属性的 Descriptor 对象上的,不在原型上。

class Animal{
    constructor(name,age){
        this.name = name
        this.age = age
    }
    // 创建一个age属性,并给他加上setter,给该属性赋值时,会运行该函数
    set age(age){
        if(age<0){
            age = 0
        }else if(age>150){
            age = 100
        }
        this._age = age
    }
    // 创建一个age属性,并给他加上getter,给该属性赋值时,会运行该函数
    get age(){
        return this._age + "岁"
    }
}
const dog = new Animal("巴顿",5)
console.log(dog.age)

相当于es5中写法如下 Object.defineProperty可定义某个对象成员属性的读取和设置

Object.defineProperty(this,"age",{
    set(val){
		//...
    },
    get(){
        //...
    }
})

(3)属性表达式:可计算的成员名

const printName = "print"
class Animal{
    constructor(type,name,age,sex){
        this.type = type
        this.name = name
        this.age = age
        this.sex = sex
    }
    [printName](){
        console.log(`【种类】:${this.type}`)
        console.log(`【名字】:${this.name}`)
        console.log(`【年龄】:${this.age}`)
        console.log(`【性别】:${this.sex}`)
    }
}
const dog = new Animal("狗","巴顿",5,"公")
dog.print()
for (const key in dog) {
    console.log(key)
}

(4)Class类表达式

const A = class { //匿名类 ,类表达式
    a = 1
    b = 2
}
const a = new A()

采用 Class 表达式,可以写出立即执行的 Class。

let person = new class {
  constructor(name) {
    this.name = name;
  }
  sayName() {
    console.log(this.name);
  }
}('ll');

person.sayName();

2、有关类的注意点

(1)类和模块的内部,默认就是严格模式。类中的所有代码均在严格模式下执行

(2)类声明不会被提升,与 letconst一样,存在暂时性死区

new A(); // ReferenceError
class A {}

(3)类的所有方法都是不可枚举的

(4)类的所有方法都无法被当作构造函数使用

(5)类的构造器必须使用 new来调用

(6)类的方法内部如果含有this,它默认指向类的实例。

class Person {
  printName(name = '小张') {
    this.print(`Hello ${name}`);
  }

  print(text) {
    console.log(text);
  }
}

const person = new Person();
const { printName } = person;
printName(); // TypeError: Cannot read property 'print' of undefined

原因printName方法中的this,默认指向Person类的实例。但是,如果将这个方法提取出来单独使用,this会指向该方法运行时所在的环境(由于 class内部是严格模式,所以 this实际指向的是undefined),从而导致找不到print方法而报错。

3、静态成员

如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用构造函数本身的成员。使用static关键字定义的成员即静态成员

class Chess{
    //使用static关键字定义的
    static width = 100
    static height = 100
    static getWidth(){
        return `width:${Chess.width}`
    }
    constructor(name){
        this.name = name
    }
}
//与使用static关键字定义一样,定义在构造函数身上
Chess.width = 100 //每个棋子都应该有宽高
Chess.getWidth()

注意

(1)使用static的字段初始化器,添加的是静态成员
(2)没有使用static的字段初始化器,添加的成员位于对象上

class Chess{
    static width = 100
    static height = 100
    step = 1 //相当于直接写在constructor构造器中
    constructor(name){
        this.name = name
        //this.step = 1 //上面代码的效果
    }
}

4、类的继承

(1)类的继承简介

Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

如果子类定义了constructor构造器,则必须在constructor的第一行通过super关键字调用父类的构造方法。如果子类没有定义constructor,则会有默认的构造器,该构造器需要的参数和父类一致,并且自动调用父类构造器

class Animal{
    constructor(type,name,age,sex){
        this.type = type
        this.name = name
        this.age = age
        this.sex = sex
    }
    static print(){
        console.log(`【种类】:${this.type}`)
        console.log(`【名字】:${this.name}`)
        console.log(`【年龄】:${this.age}`)
        console.log(`【性别】:${this.sex}`)
    }
}

class Dog extends Animal{
    constructor(name,age,sex,weight){
        super("犬类",name,age,sex)//通过super关键字调用父类的构造器
        this.weight = weight
    }
    print2(){
        console.log(`【性别】:${this.weight}`)
    }
}
const dog = new Animal("巴顿",5,"公",100)
dog.print()

注意:父类的静态方法,也会被子类继承。

(2)Object.getPrototypeOf()

可以从子类上获取父类。因此,可以使用这个方法判断,一个类是否继承了另一个类。

Object.getPrototypeOf(Dog)//返回Dog的父类Animal

Object.getPrototypeOf(Dog) === Animal //true

(3)super 关键字

super既可以当函数调用,也可以当对象使用。

a、super作为函数调用:

super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。否则 JavaScript 引擎会报错。

class A {}

class B extends A {
  constructor() {
    super();//调用A的构造器。
  }
}

注意super内部的this仍指向B。只能用在子类的构造函数之中,用在其他地方就会报错。

b、super作为对象调用:

class A {
  print() {
    console.log("我被调用了")
  }
}

class B extends A {
  constructor() {
    super();
    super.print(); // "我被调用了"
  }
}
const b = new B();

子类B当中的super.p(),就是将super当作一个对象使用。这时,super在普通方法之中,指向A.prototype,所以super.p()就相当于A.prototype.p()

注意:由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。

二、符号Symbol

1、普通符号

符号是ES6新增的一个数据类型,它通过使用函数 Symbol(符号描述)来创建。符号设计的初衷,是为了给对象设置私有属性(私有属性:只能在对象内部使用,外面无法使用)

const sym1 = Symbol("这个是描述信息")
const sym2 = Symbol("abc")
console.log(sym1,sym2)//Symbol(这个是描述信息) Symbol(abc)

符号具有以下特点:

  • 没有字面量
  • 使用 typeof 得到的类型是 symbol
  • 每次调用 Symbol 函数得到的符号永远不相等,无论符号名是否相同
  • 符号可以作为对象的属性名存在,这种属性称之为符号属性
    • 开发者可以通过精心的设计,让这些属性无法通过常规方式被外界访问
    • 符号属性是不能枚举的,因此在 for-in 循环中无法读取到符号属性,Object.keys 方法也无法读取到符号属性
    • Object.getOwnPropertyNames 尽管可以得到所有无法枚举的属性,但是仍然无法读取到符号属性
    • ES6 新增 Object.getOwnPropertySymbols 方法,可以读取符号
const sym = Symbol("这是一个符号属性")
const obj = {
  a:1,
  b:2,
  [sym]:3
}
console.log(obj)//{a: 1, b: 2, Symbol(这是一个符号属性): 3}
const syms = Object.getOwnPropertySymbols(obj)//获取符号属性
console.log(syms)//[Symbol(这是一个符号属性)]
  • 符号无法被隐式转换,因此不能被用于数学运算、字符串拼接或其他隐式转换的场景,但符号可以显式的转换为字符串,通过 String 构造函数进行转换即可,console.log 之所以可以输出符号,是它在内部进行了显式转换
const hero = (function(){
const getRandom = Symbol("用于产生随机数")
return{
   attack:30,
   hp:100,
   defence:10,
   battle(){
       //攻击,伤害:攻击力*随机数
       const dmg = this.attack * this[getRandom](0.5,2)
       console.log(dmg)
   },
   [getRandom](min,max){
       return Math.random()* (max-min)+min
   }
}
})()
console.log(hero)
console.log(hero.battle())//一个数
console.log(hero[getRandom](0.5,2))//Uncaught ReferenceError: getRandom is not defined

2、 共享符号

根据某个符号名称(符号描述)能够得到同一个符号

Symbol.for("符号名/符号描述")  //获取共享符号

const syb1 = Symbol.for("abc");
const syb2 = Symbol.for("abc");
console.log(syb1 === syb2)
const obj1 = {
    a: 1,
    b: 2,
    [syb1]: 3
}

const obj2 = {
    a: "a",
    b: "b",
    [syb2]: "c"
}

console.log(obj1, obj2);

3、 知名符号

知名符号是一些具有特殊含义的共享符号,通过 Symbol 的静态属性得到

ES6 延续了 ES5 的思想:减少魔法,暴露内部实现。因此,ES6 用知名符号暴露了某些场景的内部实现

(1)Symbol.hasInstance

该符号用于定义构造函数的静态成员,它将影响 instanceof 的判定

obj instanceof A
//等效于
A[Symbol.hasInstance](obj)
//Function.prototype[Symbol.hasInstance]
function A() {}
Object.defineProperty(A, Symbol.hasInstance, {
    value: function (obj) {
        return false; //均返回false
    }
})
const obj = new A();
console.log(obj instanceof A); //false
console.log(A[Symbol.hasInstance](obj)); //false

(2) Symbol.isConcatSpreadable:响数组的 concat 方法

该数组是否展开。默认是展开

const arr = [3];
const arr2 = [5, 6, 7, 8];
const result = arr.concat(56, arr2)
console.log(result)//[3, 56, 5, 6, 7, 8]
//-------------------
const arr = [3];
const arr2 = [5, 6, 7, 8];
arr2[Symbol.isConcatSpreadable] = false;
const result = arr.concat(56, arr2)
console.log(result)//[3, 56, [5,6,7,8]]

(3)Symbol.toPrimitive:影响类型转换的结果

该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。

class Temperature {
    constructor(degree) {
        this.degree = degree;
    }

    [Symbol.toPrimitive](type) {
        if (type === "default") {
            return this.degree + "摄氏度";
        }
        else if (type === "number") {
            return this.degree;
        }
        else if (type === "string") {
            return this.degree + "℃";
        }
    }
}

const t = new Temperature(30);

console.log(t + "!");//30摄氏度!
console.log(t / 2); //15
console.log(String(t));//30℃

(4)Symbol.toStringTag:影响 Object.prototype.toString 的返回值

class Person {
    [Symbol.toStringTag] = "Person"
}
const p = new Person();
const arr = [32424, 45654, 32]
console.log(Object.prototype.toString.apply(p));//[object Person]
console.log(Object.prototype.toString.apply(arr))//[object Array]
(5)Symbol.iterator:可以改变遍历器方法。
(6)Symbol.split:当该对象被String.prototype.split方法调用时,会返回该方法的返回值
(7)Symbol.search:当该对象被String.prototype.search方法调用时,会返回该方法的返回值。
(8)Symbol.replace:当该对象被String.prototype.replace方法调用时,会返回该方法的返回值。
(9)Symbol.match:当执行str.match(myObject)时,如果该属性存在,会调用它,返回该方法的返回值
(10)Symbol.species:创建衍生对象时,会使用该属性。
log(Object.prototype.toString.apply(p));//[object Person]
console.log(Object.prototype.toString.apply(arr))//[object Array]
(5)Symbol.iterator:可以改变遍历器方法。
(6)Symbol.split:当该对象被String.prototype.split方法调用时,会返回该方法的返回值
(7)Symbol.search:当该对象被String.prototype.search方法调用时,会返回该方法的返回值。
(8)Symbol.replace:当该对象被String.prototype.replace方法调用时,会返回该方法的返回值。
(9)Symbol.match:当执行str.match(myObject)时,如果该属性存在,会调用它,返回该方法的返回值
(10)Symbol.species:创建衍生对象时,会使用该属性。
(11)Symbol.unscopables:该对象指定了使用with关键字时,哪些属性会被with环境排除。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章