• JS高级---面向对象


    理解对象

    对象是Object的实例,js中万物皆对象,都拥有属性方法

    比如下面的两个对象通过不同的方式创建,但他们是等价的

    // 实例化对象
    let p1 = new Object();
    p1.name = "neko";
    p1.job = "anchor";
    p1.sayName = function() {
        console.log(this.name);
    }
    // 字面量对象
    let p2 = {
        name : "neko";
        job : "anchor";
        sayName() {
            console.log(this.name);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    属性的类型

    数据属性

    数据属性包含一个保存数据值的地方,值会在这里读取和写入,拥有下面的4个特性。

    • Configurable: 属性是否可以被删除和重新定义,是否可以修改其他特性,默认值为true。
    • Enumerable:属性是否可以通过for-in循环,默认值为true。
    • Writable:属性是否可以被修改,默认值为true。
    • value:属性的值。

    特性是被封装起来的,要修改数据的特性,需要调用Object.defineProperty() 方法,这个方法接受3个参数

    • 第一个参数是要修改的对象。
    • 第二个参数是属性的名称。
    • 第三个参数是特性组成的对象。

    当Configurable被修改为false时,这个对象就会被锁死,不能再调用**Object.defineProperty()**方法。

    访问器属性

    访问器属性不包含属性值,他可以用来设置和获取对象的数据属性。

    他拥有四个特性

    • Configurable:表示是否可以被删除和修改,是否可以修改特性,是否可以可以转化为数据属性,默认值为true。
    • Enumereable:表示属性是否可以被遍历,默认值为true。
    • Get:获取函数,用于读取属性值,默认值为undefined。
    • Set:设置函数,用于写入属性值,默认值为undefined。

    访问器属性不能直接定义,只能通过**Object.defineProperty()**方法定义。

    let book = {
        year_ : 2017,
        edition : 1;
    };
    
    Object.defineProperty(book , 'year' , {
        get(){
            return year_;
        },
        set(val){
        	this.year = val;
            this.edition = val - this.edition;
    	}
    })
    
    book.year = 2018;
    console.log(book.edition)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    其中get函数用于获取值,只是简单得让year的值和year_在数值上相同。

    set函数用于设置值,当year的值变化时,会将新的值作为参数传入set函数,然后调用。(我怀疑vue就是用这东西搞的)

    set和get都是可选的,只定义get意味着属性是只读的,不能修改,只有set函数则不能进行值的读取,必须做赋值操作。

    静态方法

    定义多个属性

    **Object.defineProperty()**可以同时为对象定义多个属性,包括数据属性和访问器属性,语法如下。

    let girl = {};
    Object.defineProperty(girl , {
        name : {
            value : 'neko';
        },
        age : {
            vallue : 18;
        },
        othername : {
            get(){
                retuen this.name;
            },
            set(val){
                this.name = val;
            }
        }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这样与单独定义的区别是:属性特性的默认值都会变为false。(configurable、enumerable、writable)

    特性读取

    使用**Object.getPropertyDescriptor()**可以获取到指定属性的修饰符。

    这个函数接受两个参数,第一个参数是属性所在的对象,第二个参数是属性名。

    返回值是一个由属性特性组成的对象。

    let book = {};
    Object.defineProperty(book , {
        year_ : {
            value : 2022,
        },
        edition : {
            value : 1,
        },
        year : {
            get(){
                return this.year_;
            },
            set(val){
                this.edition = val;
            }
        }
    })
    
    let ds = Object.getPropertyDescriptor(book , "year_");
    //{
    //    value : 2022,
    //    configurable : false,
    //    enumerable : false,
    //    writable : false,
    //}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    ES6在2017年新增加了**Object.getPropertyDescriptors()**方法,这个函数接受一个目标对象,返回一个属性对象,属性对象内有各个属性的特性值组成的对象。

    这个方法的本质是对**Object.getPropertyDescriptor()的再封装,就是对象的每个属性使用Object.getPropertyDescriptor()**方法。

    let book = {};
    Object.defineProperty(book , {
        year_ : {
            value : 2022,
        },
        edition : {
            value : 1,
        },
        year : {
            get(){
                return this.year_;
            },
            set(val){
                this.edition = val;
            }
        }
    })
    
    let ds = Object.getPropertyDescriptors(book);
    //{
    //    year_ : {...},
    //    edition : {...},
    //    year : {...},
    //}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    对象合并

    将源对象的属性和方法复制到目标对象上称为对象合并,也叫做混入(mixin);

    可以使用**Object.assign()**方法。

    这个方法第一个参数为要接受合并的目标对象,之后跟上若干个源对象,然后将源对象中的可枚举属性和自有属性拷贝到目标对象。

    这个方法只能实现浅拷贝,也就是拷贝地址,拷贝后的属性只是之前的引用。

    let dest = {};
    let src = { a : {} }
    
    Object.assign(dest , src);
    console.log(dest.a === src.a) // true
    
    • 1
    • 2
    • 3
    • 4
    • 5

    合并后的源对象属性会覆盖掉目标对象的同名属性。

    let a = {id : 1 , name : 'neko'};
    let b = {id : 2 , age : 16};
    
    Object.assign(a , b);
    console.log(a) //{id : 2 , name : 'neko' , age : 16}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如果在拷贝期间出错,会立刻停止执行,不会回滚到执行之前。

    对象相等

    新增加了Object.is方法,可以检测两个对象的内容是否相等。

    这个方法接收两个被比较的对象,返回一个布尔值。

    由于这个函数比较的是内容,所以可以弥补===的不足。

    例如NaN和NaN的比较,对象的比较。

    如果有多个要检查的对象,可以自定义函数利用递归比较即可

    function reis(x , ...y){
        return Object.is(x , y[0]) &&
            (y.length < 2 || reis(...y))
    }
    
    • 1
    • 2
    • 3
    • 4

    对象的增强写法

    属性值简写

    有的时候属性值(变量名)和属性名是一样的,这时候就可以简写。

    下面的两种写法是一样的。

    let name = "neko";
    // 之前的写法
    const a1 = {
        name : name,
    }
    // 增强写法
    const a2 = {
        name,
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    可计算属性

    可计算属性简化了动态属性名的定义。

    下面的两种写法是一样的。

    // 之前的写法
    let o = {
    	name : 'name',
        age : "age",
    };
    
    let p1 = {
        o[name] : 'neko',
        o[age]18,
    }
    // 增强写法
    let name = "name",
    let age = "age",
    let p2 = {
        [name] : 'neko',  // 将属性名用中括号包裹起来,可以将其作为js表达式来求解。
        [age] : 18,
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    简化方法名

    在对象内定义函数时也有增强写法。如下所示。

    let p = {
        // 过去的写法
        fun1 : function(){
            
        },
        // 增强写法
        fun2(){
            
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    注意函数的增强写法和可计算属性是兼容的。

    let fun1 = 'NekoSay';
    
    let p = {
        [fun1](){
            console.log("哪来的野猫呀【该用户已被禁言】")
        }
    }
    
    p.NekoSay(); // 哪来的野猫呀【该用户已被禁言】
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    对象解构

    对象解构是用来将对象中的属性抽出变为单独变量的操作。

    这是es6中新增加的语法,下面两种写法的效果是一样的。

    let o = {
        name : 'neko',
        age : 16,
    };
    // 之前的写法
    let girlName = o.name;
    let girlAge = o.age;
    // es6写法
    let { name : girlName , age : girlAge } = o;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    如果想使用的变量名和属性名相同,可以使用简写。

    let {name , age} = o;
    
    • 1

    如果对象中没有想要结构的值,就会赋值为undefined,也可以在解构时附上默认值。

    同时如果出现对象中不存在的值,解构喙中途停止,也不会有回滚操作。

    let {name , sex = "girl"} = o // 因为sex不存在所以直接用默认值,没有默认值就变为undefined,后面的其他属性也会停止解构
    
    • 1

    被解构的值会被转化为对象,即使是数字,字符串或布尔值也可以被解构,但是null和undefined不可以。

    let {length} = "foobar" // 6
    let { _ } = null // TypeError
    
    • 1
    • 2

    如果是为事先声明过的值解构,需要加上小括号。

    let oName , oAge;
    (let { name : oName , age : oAge } = o);
    
    • 1
    • 2

    解构赋值可以嵌套使用

    let o = {
        name : 'neko',
        job : {
            title : "player"
        }
    }
    
    let { job {title} } = o;
    console.log(title) // player
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    解构赋值也可以在函数中使用

    function printPerson(foo , {name , age} ,bar) {
        console.log(arguments); // 打印接收到的所有参数
        console.log(name,age);
    };
    
    printPerson("111", {name : 'neko' , age : 16} , "222"); 
    // ["111", {name : 'neko' , age : 16}, "222"]
    // {name : 'neko' , age : 16}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    创建对象

    在es6引入类之后对于对象创建的优化算是结束了。但是在类之前就有许多创建对象的方案,类只不过是简化这些方案。

    下面依次介绍被替代掉的这些方案。

    工厂模式

    工厂函数是一种最早的设计模式,他的示例语法如下:

    function createPerson(name , age){
        let o = new Object();
        o.name = name;
        o.age = age;
        return o;
    }
    
    let p1 = createPerson('neko' , 16);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这样虽然可以模板化得创建对象,但是存在诸多问题,比如没有对象标识符(也就是对象的类型)

    构造函数模式

    js中提供了一些原生的构造函数,例如Object()Array(),我们也可以自定义构造函数。

    下面是构造函数的语法示例:

    function Person(name , age) {
        this.name = name;
        this.age = age;
        this.sayName() {
            console.log(this.name);
        }
    }
    
    let p1 = new Person('neko' , 16);
    let p2 = new Person('amiya' , 16);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    按照惯例,我们将构造函数的首字母大写来和普通函数做区分。

    在创建实例时,需要使用new关键字来创建,要注意构造函数也是个函数,他和普通函数的区别只是调用方式的不同,它通过new关键字来调用。

    构造函数也可以当作普通法函数来调用,这样就会在window对象上挂在相应的属性和方法。

    Person('neko' , 16);
    console.log(window.name) // neko
    
    • 1
    • 2

    在使用new操作符的时候,浏览器做了以下工作:

    1. 在内存中创建一个新对象
    2. 将对象的[[prototype]]特性赋值为构造函数的prototype。
    3. 构造函数内部的this被修改为新创建的对象。
    4. 执行构造函数内的代码。
    5. 如果构造函数内有返回值,则接受,否则返回新创建的对象。

    上面创建的p1和p2中保存有不用的实例,但他们的constructor属性都指向Person构造函数。

    也可以使用instanceof操作符来获取对应的构造函数,也就是对象类型。

    p1.constructor === p2.constructor === Person  // true
    p1 instanceof Person  // true
    p1 instanceof Object  // true
    
    • 1
    • 2
    • 3

    构造函数同样存在很多问题:构造函数的每一个实例都是独立的,有一些重复的属性和方法,也都会生成两份,造成内存浪费。

    原型模式

    要理解原型模式,需要先理解原型。

    理解原型

    要理解原型就是是理解原型对象,构造函数和实例对象的关系。

    原型之间的关系比较复杂,简单画一张图理解一下。

    在这里插入图片描述

    • 类中有一个prototype属性指向原型对象。
    • 原型对象中有一个constructor属性指向类。
    • 实例对象中的prototype也指向原型对象,但这个属性被封装,只能通过__proto__属性来指向原型。
    • 在创建类时会创建出一个原型对象,实例对象共有的属性和方法会储存在原型对象中。

    ES6中提供了 Object.getPrototypeof() 方法返回实例对象内部的 [[prototype]] 属性的值,使用这个方法可以取得一个实例对象的原型。

    ES6中还提供了 Object.setPrototypeof() 方法,可以向实例对象内部的 [[prototype]] 写入值修改其对应的实例对象,但这个方法严重影响性能,不建议使用。要实现其对应的功能,可以通过 Object.create() 方法创建对象,并为其指定原型。

    let a = {
    	age : 16
    }; // 模拟一个原型对象
    
    let b = Object.create(a); // 创建新对象b,将a指定为原型
    
    • 1
    • 2
    • 3
    • 4
    • 5

    原型层级

    在通过对象访问属性时,会通过属性名进行查找,如果在实例对象上找不到对应的属性名,则会查找原型对象。

    实例对象上的属性会覆盖掉原型对象上的同名属性。

    通过调用hasOwnProperty() 方法可以确定某个属性,是来自实例对象,还是来自原型对象。

    function Person () {};
    Person.prototype.name = 'neko';
    
    let p1 = new Person();
    p1.age = 16;
    
    p1.hasOwnProperty('name'); // 来自原型,返回false
    p1.hasOwnProperty('age'); // 来自实例,返回true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    重写原型对象

    如果原型模式中包括多个对象,重复的出现propotype显得冗余,可以采用下面的方法进行重写。

    function Person () {};
    Person.prototype = {
    	name: 'neko',
    	age: 16
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这样做会导致新的原型的constructor属性指向Object而不是Person,
    可以在重写时指定constructor属性。

    function Person () {};
    Person.prototype = {
    	constrcutor: Person,
    	name: 'neko',
    	age: 16
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这样做依然有问题,原生的constructor属性时不可枚举的,因此应当使用 Object.defineProperty() 方法来定义constructor属性。

    function Person () {};
    Person.prototype = {
    	name: 'neko',
    	age: 16
    }
    
    Object.defineProperty(Person.prototype , "constructor" , {
    	enumrable : false,
    	value : Person
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    重写之后的原型将不能再影响重写前创建的对象。
    之前的对象依然指向重写前的原型。

    在对象创建之后修改原型,实例对象的值也会随之变化。

    原生对象模型

    通过prototype可以向String,Array等原生的原型对象中添加方法,但不推荐这样做,可能引发很多问题。

    推荐的做法是,创建自定义的类继承原生的原型对象。

    原型的问题

    1. 弱化了传递初始化参数的能力,使得所有的对象初始值都一样。
    2. 原型对象中的引用数据类型会公用,可能出现问题。

    对象迭代

    for-in

    • for-in 循环可以获得对象中所有的可枚举的属性(包括被封装的属性)

    • Object.keys() 方法可以实现类似的效果,不过他只能放回实例对象上的属性,不包含原型对象上的属性。

    • Object.getOwnPropertyNames 可以获得实例对象上所有属性,无论其是否可以枚举

    • Object.getOwnProtertySymbols() 方法可以获得对象上所有的以符号定义的属性名。

    function Person() {};
    
    Person.prototype.name = 'neko';
    
    let p1 = new Person();
    p1.age = 16;
    
    const s1 = Symbol('s1');
    let o = {
      [s1] : 's1',
      s2 : 's2'
    }
    console.log(Object.keys(p1)); // [ 'age' ]
    console.log(Object.getOwnPropertyNames(p1)); // [ 'age' ]
    console.log(Object.getOwnPropertyNames(Person.prototype)); // [ 'constructor', 'name' ]
    console.log(Object.getOwnPropertySymbols(o)); // [ Symbol(s1) ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    对于以上的枚举方法,枚举顺序有所差异。

    • for-inObject.keys() 枚举顺序不确定,因浏览器而异。
    • Object.getOwnPropertyNamesObject.getOwnProtertySymbols() 的枚举顺序遵守下面的规则:
      1. 先升序枚举数值键
      2. 以插入顺序枚举字符串和符号键

    迭代方法

    ES6中新增两个静态方法用于对象迭代(取得对象中各属性的值)

    • Object.values()
    • OBject.entries()

    这两个方法在进行对象迭代时,会忽略符号键。

    const girl = {
      name : 'neko',
      age : 16,
      [Symbol('k1')] : 'k1',
    }
    
    console.log(Object.values(girl)); // [ 'neko', 16 ]
    console.log(Object.entries(girl)); // [ [ 'name', 'neko' ], [ 'age', 16 ] ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    原型链

    (这里有很多东西我看不懂,就没有写出来,暂时先这样)

    js通过原型链实现了继承。
    如果一个原型对象是另一个原型对象的实例就构成了原型链。
    在这里插入图片描述
    当son实例需要一个属性时,会现在自身检索。
    如果找不到则通过__proto__向Son原型寻找。
    如果还找不到,则再通过__proto__向Father原型寻找。

    Son原型是Father原型的实例对象,重点在于Son类没有使用默认原型,而是将原型替换为了Father类的实例对象,这样做实现了继承,实例对于属性的搜索会一直持续到原型链末端。

    默认原型

    默认情况下所有的引用类型原型链末端都是Object原型对象,

    原型与继承

    确定一个实例与原型的关系有两种方法

    • instanceof操作符
    • isPrototypeOf() 原型方法
    let str = 'aaa';
    
    // 只要在原型链中出现过就返回true
    str instanceof String // true
    str instanceof Object // true
    
    // 只要在原型链中出现过就返回true
    String.prototype.isPrototypeOf(str) // true
    Object.prototype.isPrototypeOf(str) // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    原型链的问题

    原型链存在的问题有两个:

    • 首先依然是引用数据类型的问题。
    • 子类在实例化时,无法影响父类的构造函数。

    前面使用各种方法来模拟类的行为。
    es6正式引入了类,但其背后依然是使用前面的方法,本质上只是一个语法糖。
    前面的作为了解,因为有了类以后不再需要那些东西,但那些东西能帮我们理解类的原理。

    类的定义

    按照下面的语法来定义类

    class Person {
    
    }
    
    • 1
    • 2
    • 3

    注意:

    • 类不能被提升,在实例化之前不能引用。
    • 类可以由构造函数、实例方法、获取函数、设置函数、静态方法组成,但这些都不是必须的,可以定义空类。

    构造函数

    constructor关键字用于在类中定义构造函数。
    在使用new关键字创建实例时会调用这个函数,默认会自动创建一个空的构造函数。

    在使用new实例化类时,可以传入参数,这些参数会传入构造器函数中来初始化对象。

    class Girl {
    	constructor(name,age){
    		this.name = name;
    		this.age = age;
    	}
    }
    
    const m = new Girl('neko' , 16);
    // {name:'neko' , age:16}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    构造器函数就是一个特殊的函数,实例化之后也会变为普通函数,只不过在调用时依然需要使用new关键字。

    类也是一个特殊的函数,之前的原型链之间的关系在类这里依然适用。

    实例、原型和类

    通过构造器创建出的实例中的成员都是独立的,不会共享。

    为了在实例之间共享方法,可以在类中定义方法,这些方法会被附加在原型上。

    class Girl {
    	constructor(name,age){
    		// 这些都是独立的
    		this.name = name;
    		this.age = age;
    	}
    	// 这个方法会出现在原型上。
    	say() {}
    	// 特别注意不能再类中添加属性。
    }
    
    const m = new Girl('neko' , 16);
    // {name:'neko' , age:16}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    类也支持获取get和set函数,语法跟普通对象一样。

    class Girl {
    	set name(newVal){
    		this.name_ = newVal;
    	}
    
    	get name(){
    		return this.name_;
    	}
    }
    
    const m = new Girl();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    类可以定义静态方法,使用static关键字,静态方法只能由类本身来调用,一个类只能由一个静态方法。

    class Girl {
    	static say(){}
    }
    
    Girl.say();
    
    • 1
    • 2
    • 3
    • 4
    • 5

    虽然类在定义是不允许添加属性和方法,但可以在外部进行添加。

    class Girl {
    	
    }
    
    Girl.age = 16; // 在类上添加
    Girl.prototype.age = 18; // 在原型对象上添加
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    继承

    使用extends关键字进行继承操作,不仅可以继承类,还可以继承构造函数。

    super

    子类可以在构造函数和静态方法中使用super,super必须写在第一行。

    在构造函数中使用super会调用父类的构造函数。
    在静态方法中使用super会调用父类的静态方法。

    class Girl {
    	constructor(name,age){
    		this.name = name;
    		this.age = age;
    	}
    	static say() {}
    }
    
    class GirlFriend extends Girl {
    	constructor(name,age){
    		super(name,age);
    	}
    
    	static cry(){
    		super.say();
    	}
    }
    const m = new GirlFriend('neko' , 16);
    // {name:'neko' , age:16}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    抽象基类

    有时候会需要一个不会被实例化,但需要被继承的类,这种类称为抽象基类。
    js没有提供相应的语法,但可以通过 new.target 实现。

    class Base {
    	constructor(){
    		if(new.target === Base){
    			throw new Error("不能创建抽象基类")
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在抽象基类中可以进行检查,要求子类必须包含某些方法。

    class Base {
    	constructor(){
    		if(new.target === Base){
    			throw new Error("不能创建抽象基类")
    		}
    
    		if(!this.foo){
    			throw new Error("子类中必须有自己的foo方法")
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    继承内置类型

    继承js内置的类可以便捷的扩展功能

    class MyArray extends Array {
    	myfun(){}
    }
    
    • 1
    • 2
    • 3

    有些内置的方法会放回新的实例,默认会通过调用者的类来创建新的实例。

    const a1 = new MyArray(1,2,3,4,5);
    
    let a2 = a1.filter(x => !!(x % 2)) // 返回值为MyArray类型
    
    • 1
    • 2
    • 3

    通过覆盖Symbol.species访问器可以修改这一行为

    class MyArray extends Array {
    	static get [Symbol.species] () {
    		return Array;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
  • 相关阅读:
    MySQL事务/事务与数据库底层数据/多点回滚/隔离级别/悲观锁和乐观锁/锁模式和分类/相关锁总结/JDBC事务实现
    CSS代码收集(持续更新)
    上个厕所的时间了解链路追踪基本概念
    Spring Boot 面试题及答案整理,最新面试题
    基于JAVA的在线考试平台的设计与实现,正在做的同学可以参考一下【数据库设计、源码、开题报告】
    基于 Android 的文件同步设计方案
    【源码解析】Spring源码解读-beanFactory和Bean的后置处理器流程
    HCIP(第十四天)
    loggie 编码以及换行符测试
    BizWorks 应⽤平台基于 KubeVela 的实践
  • 原文地址:https://blog.csdn.net/m0_66711291/article/details/126077380