一、类
在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 一样,在“类”的内部可以使用get
和set
关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。存值函数和取值函数是设置在属性的 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)类声明不会被提升,与 let
和 const
一样,存在暂时性死区
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]