JS Object:哈希表的"影分身之术"和原型链的千层套路

57 阅读20分钟

快慢元素

Array 是 Object 的子集,其实这一部分和 Array 的快慢元素基本一致

  • 快元素:当 object 结构稳定的时候,被存储在连续的内存中
  • 慢元素: 使用哈希表存储

值得注意的是,当我们使用数字作为key,可能出发数组的特殊优化方式(如纯数字类型,采用SMI格式优化内存空间)

隐藏类优化

当Object的类型结构比较稳定,则会复用隐藏类,反之,如果结构变化多,则会触发隐藏类的不断变更

为什么要隐藏类

由于JS是动态类型,一个Object我们可以动态做很多修改,这就导致在内存中计算机无法很快速的定位,不像强类型的语言(比如C),可以直接通过访问n号内存来实现快速访问。动态类型需要动态的计算偏移量,导致性能损耗。

  • 隐藏类为相同结果(顺序也相同)生成共享的类,预先分配属性的便宜量(类型静态语言中的快速访问)
  • 同时 V8 引擎会缓存隐藏类,加速重复访问
  • 隐藏类之于对象本身结构有关系,和他的原型没有什么关系,原型修改不触发隐藏类修改
  • 当属性修改的时候会出发隐藏类的分裂,重新生成隐藏类会造成性能的下降

开发者最佳实现

  1. 尽量保持对象的结构稳定,属性和顺序
  2. 尽量避免破坏性操作
  3. 在原型方法上执行定义方法而不是实例

下面是 JS 对象 & 原型 & 类 相关基础知识,长文预警,只想了解JS在阴暗的角落藏了什么的可以到此为止。下面是反复的炒冷饭。

对象基础知识

设置对象的属性

  • Objectl.defineProperty(obj,argName,{属性设置})

  • Object1.defineProerties (obj1:{},obj2{})

    •   其中数据属性设置包括

  • [[Configurable]] 是否可以删除或重新定义,是否修改属性

  • [[Enumerable]] 是否可以枚举,是否能被for in 遍历,原生的.constructor 属性是不可枚举的,但重写的默认为true,所以重写constructor的时候要设置此属性为false

  • [[Writeable]] 是否可以被修改 ,默认为 true

  • [[Value]] 真实值

    •   以下是访问器属性,包括

  • [[Configurable]] 是否可以删除或重新定义,是否修改属性

  • [[Enumerable]] 是否可以枚举,是否能被for in 遍历

  • [[Get]] 读取函数

  • [[Set]] 修改函数

查看对象属性

  • Object.getOwnPropertyDescriptor(obj,argname)
  • Object.getOwnPropertyDescriptors(obj)

对象方法

  • 合并对象

    • Object.assign()
  • 对象标识相等判定

    • Object.is(obj1,obj2)
  • 对象迭代

    • Object.entries() | Object.values() 分别返回键值对和值
    • Symbol 属性会被忽略

内置对象

引用类型:Object , Array ,RegExp , Date

包装类型:String ,Number ,Boolean ….

Object

const user = new Object ()
//静态方法
Object.keys//只能对Object用
Object.keys(user)//o是实例化对象
Object.values(user)
//拷贝
const newuser = {}
Object.assign(newuser,user)
Object.assign(user,{gender :female})//用来给对象添加属性

Array

cosnt arr = new Array()
//实例方法
arr.forEach()//遍历数组,不返回数组,用来查找遍历元素
arr.filter() //过滤数组
arr.map()//迭代数组,返回数组,用于处理并得到新的数组
arr.reduce(function(上一次的值prev,当前的值current){
return prev + current},起始值start)//用于返回累计处理的结果,常用于求和(返回prev + current + start)
arr.join()
arr.find(callback(){})
arr.some()
arr.concat ()
arr.sort()

arr.splice()

arr.reverse()
arr.findIndex()

栈方法和队列方法

栈是后进先出:LIFO (Last-in-first-out)

队列方法是先进先出 : FIFO ( First-in-first-out)

  • arr.push('element1','element2' ) 推入一项或者多项
  • arr.pop() 弹出栈顶一项
  • arr.shift() 删除数组的第一项并返回,就是弹出队首一项
  • unshift('element1','element2') 向数组前面推入一个或多个元素

push 和 pop 可以模拟栈数据结构,数组的开头是栈底 push 和 shift 可以模拟正向队列数据结构,队的开头是数组尾、 正向就是从数组尾部推入新元素 unshift 和 pop 可以模拟反向队列结构,队的开头是数组开头

排序方法

  • arr.reverse() 直接在原数组操纵

  • arr.sort() 直接在原数组操作,接受一个比较函数

    • 此函数接受两个参数,用于比较前一个和后一个元素
    • 函数返回 正数 说明需要换位 element1 < element2 降序为例
    • 函数返回 负数 说明不用换位 element1 > element2
    • 函数返回 0 说明不用换位element1 === element2

操作方法

  • 数组打平 arr.concat(接受元素或者数组) 复制arr的副本并将里面的元素/数组里的元素逐个添加到副本中

    • 强制打平和禁止打平 [Symbol.isConcatSpreadable]:true|false属性
    •   let color5 = {
            [Symbol.isConcatSpreadable]: false,//禁止打平
            0: '3',
            1: '4'
        }
        console.log(color2.concat('1', '2', color5))
      
        //强制打平
        let color6 = {
            [Symbol.isConcatSpreadable]: true,//禁止打平
            0: '3',
            1: '4'
        }
        console.log(color2.concat('1', '2', color6))
      
  • 左闭右开切片 arr.slice(a,b)

    • 不影响原来的数组
    • 如果只有一个参数就是从a到最后面
  • 最强大arr.splice()

    • 删除用 arr.splice(0,2) 从什么位置开始删除多少个元素
    • 插入用 arr.splice(2,0,'red','green') 从2号位开始删除0个元素并插入后面的两个元素怒
    • 替换用 arr.splice(2,1,'red','green') 从2号位开始删除一个元素,然后再后面添加这两个元素

查找方法

  • 严格查找 ’ === ‘

    • arr.indexOf() 和下面两个一样都接受两个参数 : 要查找的元素和起始搜索位置
    • arr.lastIndexOf() 返回下标
    • arr.includes() 返回布尔值
  • 断言查找

    • 就是说返回第一个符合条件的元素,接受一个判断函数和一个可选的指定this参数
    • arr.find() 返回元素
    • arr.findIndex() 返回元素的索引
    • 其判断函数返回真表示找到匹配元素,否则为假

迭代方法

  • array.function(callbackFn,thisArg)

    • array.every() 返回一个布尔值、
    • array.some() 返回一个布尔值
    • array.filter() 返回true对应的所有项
    • array.forEach() 对所有项都执行callbackFn() 没有返回值
    • array.map() 映射,对每一项都执行callbackFn() 返回callbackFn的返回值组成的新的数组

归并方法

  • 归并方法接受两个参数,一个是归并函数,一个可选的以之为归并起点的初始值

  • array.reduce()

  • array.reduceRight 反向reduce

    • 归并函数接受四个参数,上一个归并值,当前值,当前项索引,数组本身
    • functionReduce(prev,current,index,array) => {}
    • 上一次值pre当前值current返回值
      1startarr[0]start + arr[0]
      2start + arr[0]arr[1]start + arr[0] +arr[1]
      3start + arr[0] +arr[1]arr[2]start + arr[0] +arr[1] +arr[2]

注意如果遍历的数组里面放的是对象,初始值要写0,否则就会变成数加对象

String

  • 实例方法String.prototype.func()
//实例方法
str = 'pink,red'
str.split('')//['pink','red'] 和.join(',')相反
str.substring(indexstart[,indexEnd])//返回一个子集,前闭后开
str.substr()//避免使用
str.startsWith(检测的索引号)//是否用给定的字符串开头
str.endsWith()
str.includes ()//判断字符串是否包含另一个字符串
str.matchAll()
  • 例子
const reg = /<li>.
*?<a>(.*
?)<\/a>.*?<p><\/p>/sg
const result = str.matchAll(reg)

可选链操作符

在函数的参数是对象的时候,我们如果要使用对象里面的某个参数,我们必须先判断这个参数是否传入,防止没有传入的时候报错

  • 传统写法
function main (config) {
        const dbHost = config && config.db && config.db.host
        console.log(dbHost)

}
main({
db:{host:'111',
username:'tony'},
cache:{
host:'222',
usernae:'mike'
}
})
  • 可选操作符写法
function main (config) {
        const dbHost = config?.db?.host
        console.log(dbHost)}

构造函数和原型

构造函数

  • 创建对象的三种方式:

  • 字面量方式

    •   const o ={
        name : 'pink'
        }
      
  • new创建

    •   const obj = new Object({uname: 'pink'})//实例化
      
  • 自定义构造函数创建对象

    • 构造函数是特殊的函数,用来初始化对象

    •   function Pig (name ,age, gender ) {
        this.name = name
        this.age = age
        this.gender = gener
        //不需要写return 返回的就是一个对象因为有new实现返回新对象
        }
        const Peppa = new Pig('佩奇',6,'女')
      
  • 为了区分构造函数和其他函数:约定

    • 命名以大写字母开始

    • 创建对象的时候一定要new一下

实例对象和实例方法

  • 实例成员:实例对象上的属性和方法就是实例成员

  • 在实例对象中新加的属性和方法也算实例成员

  • 在构造函数的属性和方法叫做静态成员(静态属性和静态方法)只有对构造函数才能使用的

    •   function Pig (a, b){}
        Pig.fun = () => {console.log(这是一个静态方法)}
        Pig.con = '这是一个静态属性'
      

包装类型

在js底层中,对基本的数据类型进行了包装,所以这些基本数据类型也有方法和属性

const str = 'pink'
包装成了:
const str = new String('pink')

js中所有数据基本都可以由构造函数创建

  • 在调用这原始的属性的时候,由于原始值是没有属性的所以要经历下面三步

    • 创建一个String/Boolean/Number类型的实例
    • 调用实例实例上的特定方法
    • 销毁实例

对Number原始值和原始值包装类型用typeof 和 instanceof 会得到不一样的结果,不建议直接实例化Number

String / Boolean /Number

  • Boolean

    • 重写valueOf()返回原始值true或者false
    • 重写toString()返回’true‘,’false‘
    • lef falseBoolean = new Boolean(false);falseBoolean && true === true
    • 上面这个语句极易引起误会,因为falseBoolean是false封装的一个对象,对象会被当作true,所以boolean比较少用
  • Number

    • 重写valueOf() 返回原始值
    • 重写toLocaleString(可以提供进制) 返回数值字符串
    • 重写toString()同上
    • 提供toFixed()方法,格式化为字符串,接受指定小数点位
    • 提供toExponential()科学计数法,接受指定小数位数
    • 提供toPrecision()根据情况选择合理的输出结果,接受指定的总位数
    • 提供isInteger()安全整数 接受一个Number 判断是否真的为整数,1.00 → true 1.01 → false

原型

解决构造函数里面会浪费内存的问题(就是同一个函数再不同的对象里面不是同一个地址)

prototype

每个构造函数都有一个prototype属性,指向另一个对象,也成为原型对象 ,这个对象可以挂载函数,实现函数的共享

function Star(uname,age) {
this.uname = uname
this.age = age
//this.sing = function () {}这里的函数每次实例化都会开辟新的空间
}
Star.prototype.sing = function (){//挂载在原型上就不会再次开辟
        console.log('唱歌')
}
const ldh = new Star('ldh',55)
const zxy = new Star('zxy',58)
console.log(ldh.sing === zxy.sing)//true 这个函数是共享的

总结:公共属性写到构造函数里面,公共方法写在原型里面

构造函数和对象原型里面的this都是指向当前实例化的对象

  • demo

    • 给数组拓展方法
    •   Array.prototype.max= function () {
                return Math.max(...this)//展开运算符
        }
        Array.prototype.sum = function() {
                return this.reduce((prev,item) => prev + item ,0)
        }
      

constructor

构造器constructor,原型对象prototype里面都有一个constructor属性,指向构造函数,就是指回去了

Star.prototype.constructor === Star//true

  • 覆盖原型(prototype)时注意

    •   Array.prototype = {
        max :function (){}
        sum :function (){}
        }//这种方法会找不到爹constructor
      
  • 正确写法

    •   Array.prototype = {
        constructor:Star,
        max :function (){},
        sum :function (){}
        }
      

对象原型 proto

实例化对象是如何访问原型prototype的?注意区分原型对象和对象原型 __proto__是只读的,[[prototype]]意义相同

Star.prototype === ldh.
__proto__
//true
ldh.__proto__.prototype === Star

原型链

  • 类比作用域链,这是一种查找规则
  • 每个构造函数的prototype(原型对象)里的的__proto__(对象原型0指向Object.prototype,Object.prototype.__proto__是null

原型链检测

instanceof

检测某个对象是否在另一个对象的原型链下 (是否属于)

ldh instanceof Object //true
ldh instanceof Array //false
Array instanceof Object //ture
isPrototypeOf()

检测一个对象的原型链下是否有给定的对象

Object.prototype.isPrototypeOf(obj)

继承

原型 继承

js中大多是借助原型对象来实现的

const Person = {
eyes : 2,
head : 1
}
function Women () {}

Woman.prototype = Person
//可以继承Person的属性,但是这一步错误
Woman.prototype.constructor = Woman //把她指回原型

出现问题,因为women和man的原型是一样的,所以给Women的原型挂载的函数挂载在Person上,Man也会继承:就是说**Woman.prototype = Person** ,这样的写法会导致子类影响 父类,不可取,要new一下让父类变成实例,这样就不会影响其他子类去继承了

Women.prototype.baby = function () {}//不能这样做
Man.baby//存在
  • 解决方法
//父类
function Person() {
this.eyes = 2 ,
this.heads = 1
}
//子类

Man.prototype = New Person()

Woman.prototype = New Person()//这样就隔离了两个prototype就可以挂载baby方法了
Woman.prototype.baby = function() {}

盗用构造函数 继承

Function.prototype.call()

function.call(thisArg, arg1, arg2, ...)

指定一个this值和单独给出一个或者多个参数来调用函数

Function.prototype.apply()

apply(thisArg)

apply(thisArg, argsArray)

与call的不同就是这个参数是一个数组形式

  • 用call实现盗用继承
  • 优点是可以传参了
function Father(wife) {
    this.members = ['a', 'b']
    
this.wife = wife

}
function Son(wife) {
    
Father.apply(this, [wife])

}
const son1 = new Son('d')
const son2 = new Son('nichiol')
son1.members.push('c')
console.log(son1.members, son2.members)
console.log(son1.wife, son2.wife)
  • 缺点是父类的方法必须写在构造函数上,子类无法访问父类原型上的方法,所以函数不能重用
function Father(wife) {
    this.members = ['a', 'b'],
        this.wife = wife,
        this.say = function () {
            console.log('saying something')
        }
    console.log('abc')
}
Father.prototype.run = function () { console.log('running') }
function Son(wife) {
    Father.apply(this, [wife])
}
const son1 = new Son('d')
const son2 = new Son('nichiol')
son1.members.push('c')
console.log(son1.members, son2.members)
console.log(son1.wife, son2.wife)
son1.say()
son2.run()//TypeError: son2.run is not a function

组合 继承

就是组合原型链法和盗用法,用原型链来继承方法,用盗用法继承属性

  • 实现了可传入参数,继承父类属性及父类原型方法
  • 子类原型上搭载了新函数,可以共享
function Father(wife) {
    this.members = ['a', 'b'],
        this.wife = wife,
        console.log('abc')
}
Father.prototype.run = function () { console.log('running') }
function Son(wife) {
    Father.apply(this, [wife])
}
Son.prototype = new Father('becovered')
Son.prototype.constructor = Son
Son.prototype.see = function () { console.log('seeing') }
  • 缺点,效率差一点:因为:Son.prototype = new Father() 继承了父类的方法和属性,此时子类的 原型也就是Father实例上是有wife和member属性的,但是在调用Father.apply(this.[wife]) 的时候,wife属性会被复制到Son构造函数上,也就是说Son的实例和原型上都会存在一个wife对象,实例上(构造函数上的)的wife会覆盖原型上的

class 类

通过class关键字来构建类,其实现的功能和构造函数差不多, 视为一个语法糖,实际上他是一个特殊的函数instanceof function => true

声明和使用

  • 和构造函数一样,类名约定大写开头来区分普通函数/实例和类

  • 类的构成包括 (都是非必须的)

    • 构造函数方法 constructor() 固定名字
    • 获取函数get()
    • 设置函数set()
    • 静态类方法,实例无法访问的 static function(){}
  • 声明方法

    • 类声明class Person()
    • 类表达式const Animal = class()
    • 类和函数的区别: 函数的声明可以提升但是类的声明是不能的,函数受限于函数作用域而类限制于作用域 类和构造函数的区别: 构造函数不用new 实例化只会用window(全局)当作this,类不用new实例化会报错,包括类中的constructor当作构造函数来使用的时候也要加new
    • 声明同时实例化
    •   const czk = new class Person {
         constructor (name){
        this.name = name}
        }('czk')
      
    •   class Phone {
            constructor(brand, price)//名字固定,但是不写也是合法的,只是没有初始化的属性而已
            {
                this.brand = brand
                this.price = price
            }
            call() {
                console.log("打电话啊")
            }
        }
        let OnePlus = new Phone('one+', 2999)
        OnePlus.call()
        console.log(OnePlus.price)
      
    • 注意constructor是固定名字的,每次实例化就会执行这个函数,相当于python中的init

类的实例化过程

  • 在内存中创建对象

  • 对象内部的[[prototype]]指针被赋值

  • 函数内部的this被指向这个新对象

  • 执行构造函数内部的代码,给新对象添加属性

  • 构造函数返回非空对象,那就返回这个对象;否则返回刚新创建的对象

    • 类构造函数默认返回this,这就是实例化的对象
    • 如果返回的不是this,会导致instanceof 搜查不出来

如果let p1 = new Person() 那么: p1 instanceof Person.constructor === false 如果let p1 = new Person.constructor() 那么: p1 instanceof Person === false

原型方法

  • 定义在constructor中的函数reading和定义在类块中的seeing函数都可以直接通过实例化来访问

  • 其中定义在类块中的函数会挂载在Person的prototype

    • 定义在constructor上的函数是挂载在实例上的
    • (属性不论写在哪里都是挂载在实例上的) 用红宝书的话说就是:所有的成员都不会在原型上共享,定义在constructor外面的函数可以被共享
    •   class Testing {
                    constructor(a, b) {
                        this.a = a
                        this.b = b//属性都是定义在类上
                        this.ConstructorFn = function () {
                            console.log('This is a function defined in constructor')
                        }//定义在类上
                    }
                    solidArg = 'solidArg'
                    solidFn() {//定义在原型上
                        console.log('this is a solidFn defined in class body')
                    }
                    static staticArg = 'staticArg'
                    static staticFn() {
                        console.log('this is a static function')
                    }
                }
      
    •   console.log(test.ConstructorFn === test2.ConstructorFn)//false
        console.log(test.solidFn === test2.solidFn)//true
      
  • 放在constructor中的属性可以被 **super(element1,element2)** 继承

    •   class Person {
            constructor(name) {
                this.name = name;
                this.reading = () => { console.log('instance') }
            }
            reading() { console.log('prototype') }
            seeing() { console.log('seeing') }
        }
        let czk = new Person('Chenzikai')
        czk.reading()// instance
        Person.prototype.reading()// prototype
        czk.seeing()// seeing
      
  • 红宝书中说方法可以定义在构造函数和类块中但是不能在类快中给原型添加原始值或者对象作为成员数据,现在已经可以了

    •   class Animal {
            name = 'dog'
        }
        const dog = new Animal()
        console.log(dog.name)//dog
      

获取和设置方法

  • set functionname() | get functionname()

静态成员 static

静态成员直接定义在类本身上,而不是像其他方法定义在 prototype 上 静态成员只能定义在类块中,不能定义在constuctor上

  • 实例对象中的属性是和构造对象中的属性不相通的,和原型是相通的
function Phone(){}
Phone.name = 'a' //这个就相当于静态属性,实例不能访问,是里访问的是prototype上的
Phone.change = function() {}
let nokia = new Phone()
console.log (nokia.name)//undefine
Phone.prototype.size = '5.5' 
nokia.size//5.5
  • class : 对应的静态属性和方法在es6+中这样写
class Phone {
    constructor(brand, price)//名字固定
    {
        this.brand = brand
        this.price = price
    }
        static name = 'nokia'//实例对象不能访问
  static function() {}
}

红宝书中说每个类只能有一个静态对象,现在没有这个限制了

静态对象非常适合用来做实例工厂,就是返回实例的一个类,但是实例类不需要再拥有这个工厂能力

继承

构造函数中的继承

function Phone(brand, price) {
    this.brand = brand
    this.price = price
}
Phone.prototype.call = function () {
    console.log('我能打电话')
}
function smartPhone(brand, price, color, size) {
    Phone.call(this, brand, price)//改变this的指向,指向外面这个this
    this.color = color
    this.size = size
}
smartPhone.prototype = new Phone()
smartPhone.prototype.constructor = smartPhone//注意这里要指回去
smartPhone.prototype.playGame = function() {
    console.log ('我还可以打游戏')
}//给子类指定方法

类中的继承

类不仅可以继承 类还可以继承构造函数 派生类可以通过原型链访问到类和原型上定义的方法和成员

//class实现
class Phone {
    constructor(brand, price) {
        this.brand = brand
        this.price = price
    }
    call() {
        console.log('我可以打电话')
    }
}
class smartPhone extends Phone {//这里就已经可以用父类的方法了
    constructor(brand, price, color, size) {
        super(brand, price)//继承父类的
        this.color = color
        this.size = size
    }
    photo() {
        console.log('可以拍照')
    }
    call() {
        console.log('重写父类的call方法')
    }
}
const sp = new smartPhone('xiaomi', 1999, 'red', '5.5')
sp.call()

不可以在调用super之前引用this(当然是在contructor,因为contructor定义了此实例的this)在类块中是不用写this的,直接name = ???就行

super

super是有点特殊的语法

  • 基本语法

  • super([arguments]) // 调用父类的构造函数

  • super.propertyOnParent

  • super[expression]

    • 作为函数调用super(...args)

      • 在派生类的构造函数中contructor 中可以用函数形式
      • 调用父类的构造函数并绑定**公共字段,**其中super的参数就是给父类传参
      • 然后派生类可以进一步修改this
    • 在子类的构造函数中必须出现 **super()** 或者 返回一个对象 在子类中super() 调用父类构造函数,然后才能出现this

    • 作为属性查询super.prop|super[expr]

    • 不能单独调用console.log(super)

  • super() 放在**子类的constructor()**中,用来调用父类的构造函数,调用父类构造函数之后才能再对子类的this做修改和添加

  • super.FatherPrototypeFn 用于在子类的声明字段调用父类的方法,包括静态方法和定义在prototype上的方法

    •   class Father {
            constructor(arg1, arg2) {
                this.arg1 = arg1
                this.arg2 = arg2
            }
            static solid = 'solid'
            
        static staticfun() {
                return 'this is a solid function'
            }
            protoFn() {
                return "this is a function define in Father prototype"
            }
      
        }
        class Son extends Father {
            constructor(arg1, arg2, arg3) {
                super(arg1, arg2)
                this.arg3 = arg3
            }
            //如果我要在类中定义一个和父类的静态方法有关的函数就得用super
            static sonfunc() {
                return 
        super.staticfun()
         + ' in son class'
                // console.log('sonfunc')
            }
            sonProtoFn() {
                return 
        super.protoFn()
         + ", however it is now on Son's prototype"
            }
        }
      
  • 下面是一种错误用法

    •   class Son extends Father {
            constructor(arg1, arg2, arg3) {
                super(arg1, arg2)
                this.arg3 = arg3
                this.itselfFn = () => {
                    return super.itselfFn() + arg3//这个函数是定义在构造函数上的,也就是是挂载在实例上而不是在原型上的,所以无法用super调用
                }
            }
            //如果我要在类中定义一个和父类的静态方法有关的函数就得用super
            static sonfunc() {
                return super.staticfun() + ' in son class'
                // console.log('sonfunc')
            }
            sonProtoFn() {
                return super.protoFn() + ", however it is now on Son's prototype"
            }
        }
      
    • 定义在父类的构造函数上的是要挂载在实例上的函数,而不是在原型上,所以是不能够用super.FatherprototypeFn()调用的

get 和 set

get是读取的时候触发的函数,set的时候触发的函数,一般用于判断赋值是否合法

class Phone () {
        get price() {
                console.log('属性被修改')//读取这个属性的时候会执行这个函数
                return 'iloveyou'//返回值就是修改值

        }
        set price(newVal)//一定要有一个参数
                {
                console.log('价格属性被修改')

        }
}
let s = new Phone ()
s.price = 'free'//会执行set那一行
console.log(s.price)//会执行get那个函数

抽象基类

定义一个只供继承的类,本身不能被实例 new.target 会保存所有通过new关键字调用的函数和类

  • 在抽象基类的contructor函数中添加
class basicClass {
    constructor (){
        console.log(new.target)
        
if (basicClass === new.target){
            throw new Error('basicClass can not be directly instantiated')
        }

    }
}
class Son extends basicClass{
    constructor (name){
        super()
        this.name = name
    }
}
let basic = new basicClass ()
let son = new Son()
  • 以此类推,我们可以contructor要求子类必须声明某个方法
class basicClass {
    constructor() {
        console.log(new.target)
        if (basicClass === new.target) {
            throw new Error('basicClass can not be directly instantiated')
        }
        
if (!this.nessesary) {
            throw new Error('the function nessesary must be define')
        }

    }
}
class Son extends basicClass {
    constructor(name) {
        super()
        this.name = name
    }
    nessesary() {
        console.log("sessesary was defined")
    }
}

继承内置类型

某些类具有内置方法,其中一些方法可能会返回新的实例 这个实例与调用这个方法的类默认是同一个类型,如果想要修改这种行为,覆盖 Symbol.species 即可

//继承内置类型
class superArray extends Array {
    //这种Array添加了一个新方法,叫做superArray
    superman() {
        console.log(this, "above is a super array")
    }
}
const a1 = new superArray("tony", 1, 2, 3, 4, 5)
a1.superman()
const a2 = a1.filter((val) => {
    return val >= 3
})
console.log(a2 instanceof superArray)
  • 这里这个a2还是superArray,说明a2还是可以继续用superman方法
  • 如果我们希望只有a1是superArray,用a1造出来的实例是普通Array就要覆盖Symbol.species
class superArray extends Array {
    //这种Array添加了一个新方法,叫做superArray
    
**static get **
**Symbol.species**
 {
        return Array
    }

    superman() {
        console.log(this, "above is a super array")
    }
}
const a1 = new superArray("tony", 1, 2, 3, 4, 5)
a1.superman()
const a2 = a1.filter((val) => {
    return val >= 3
})

console.log(a2 instanceof superArray)//false
a2.superman()//TypeError: a2.superman is not a function

类混入

将不同类的行为混合到同一个类

  • 如果只需要不同类的属性,只需要用Object.assign复制一下

    • Object.assign()静态方法将一个或者多个源对象中所有可枚举类型和自有属性复制到目标对象,返回一个目标对象
    • 本质上就是在源对象上使用[[get]] 在目标对象使用 [[set]]
    • 相同键名将被覆盖
    • 不可枚举属性和原型链上的属性不会被复制
    • 复制的时候基本类型会被封装成对象,所以undefine和null会被忽略
    • 还有更多深入了解,用到的时候再学
  • 问题是我们现在要混合的不止是类的属性,还有方法行为,这就要自己实现混合表达式

提要,class a extends b 这个b只要是任何可以求值为构造函数的表达式都行

  • 方法一:可嵌套函数
//混合类,将多个类的行为混合到一个超类中
//方法一,嵌套继承
//目标类
class vehical { }
//需要三个函数 每一个函数负责混合一个类
function Amixin(SuperClass) {
    return class extends SuperClass {
        A() {
            console.log('a')
        }
    }
}
function Bmixin(SuperClass) {
    return class extends SuperClass {
        B() {
            console.log('b')
        }
    }
}
function Cmixin(SuperClass) {
    return class extends SuperClass {
        C() {
            console.log('c')
        }
    }
}
let SuperClass = Amixin(Bmixin(Cmixin(vehical)))
//class SuperClass extends Amixin(Bmixin(Cmixin(vehical))) {}效果一样
let car = new SuperClass()
car.A()
car.B()
car.C()

从父类到子类,反直觉的,方法和行为是越来越多的

在类上定义迭代器和生成器

//在类上定义一个生成器和默认迭代器
class GenAndIter {
    
//在原型上定义

    * generator() {
        yield 1;
        yield 2;
        yield 3;
    }
    
//在类上定义(静态方法)

    static * generator1() {
        yield 4
        yield 5
        yield 6
    }
    
//定义一个默认迭代器让这个类称为可迭代对象

    *
Symbol.iterator
 {
        yield 7
        yield 8
        yield 9
    }
}
const myGen = new GenAndIter()
for (let i of myGen.generator()) {
    console.log(i)
}
for (let i of GenAndIter.generator1()) {
    console.log(i)
}
for (let i of myGen) {
    console.log(i)
}

部分插图来自网络,侵删

笔者才疏学浅,各位读者多多担待,不吝赐教。