现代JavaScript教程学习笔记(三)

115 阅读4分钟

现代JavaScript教程学习笔记

这是我学习现代JavaScript教程的笔记,他们的内容确实好,有一种相见恨晚的感觉,当时要是初学js能遇到就好了。。。 没有抄袭,只是笔记,并尝试自己稍微讲一下,如果涉及侵权,会立刻删除

对象属性配置

属性标志和属性描述符

对象中的属性不仅仅是一个值,它还有属性标志, 所以属性有value writable enumerable configurable四个特性

得到属性描述符

let user = {
    name: 'john'
}
console.log(Object.getOwnPropertyDescriptor(user, 'name'))

定义

let user = {
    name: 'john'
}
console.log(Object.getOwnPropertyDescriptor(user, 'age', {
    value: 22,
    writable: true
}))

writable: 是否可改 enumerable: 是否可枚举 configurable: 是否可配置,不可配置则不能删除,特性也不能修改但此时writable可由true改为false

配置多个属性
let user = {}
Object.defineProperties(user, {
    name: {
        value: '2',
        writable: true
    },
    age: {
        value: 22,
        writable: true
    }
})
console.log(Object.getOwnPropertyDescriptors(user))

其他方法

let user = {}
Object.preventExtensions(user) //禁止添加
Object.seal(user) //禁止添加删除,现有都为configurable:false
Object.freeze(user) //禁止添加更改删除,现有configurable:false,writable: false
Object.isExtensible(user) //这三个是检测上面的
Object.isSealed(user)
Object.isFrozen(user)

getter setter

之前的是数据属性,现在是访问器属性

let obj = {
    username: 'ww',
    get fullName() {
        return this.username
    },
    set fullName(value) {
        this.username = 'a' + value
    }
}
console.log(obj.fullName)
obj.fullName = 'k'
console.log(obj.fullName)

访问器属性有四个特性 get set configurable enumerable

原型与继承

js中的继承不同于java,其继承基于原型链,感觉挺灵活的,也挺绕的 [[Prototype]]是对象的原型 我们一般用__proto__操作,__proto__是它的set get

let animal = {}
let rabbit = {}
rabbit.__proto__ = animal

当我们使用一个属性,就会顺着原型链往上找,在哪里找到就在哪里停下 如果一个函数用的this, this与原型链无关,与调用者有关

let animal = {
    eats: true,
    isEating() {
        return this.eats
    }
}

let rabbit = {
    eats: false,
    __proto__: animal
}

console.log(rabbit.isEating())
获取属性

Object.keys(obj)返回自己的属性 for in 则返回所有,包括原型 如果我们想循环只有自己的, obj.hasOwnProperty(property)可以判断

let animal = {
    eats: true
  };
  
  let rabbit = {
    jumps: true,
    __proto__: animal
  };

  for (let prop in rabbit) {
      let isOwn = rabbit.hasOwnProperty(prop)

      if (isOwn) {
          console.log(prop)
      } else {
          console.log('*', prop)
      }
  }

F.prototype

new F(), 构造函数有prototype属性,new F()之后,会让新创建的对象的[[Prototype]]为构造函数的prototype(需要F.prototype是一个对象)

let animal = {
    eats: true
}

function Rabbit() {

}

Rabbit.prototype = animal

let rabbit = new Rabbit()
console.log(rabbit.eats)

F.prototype属性有一个constructor属性指向F,所以两个家伙感觉是相互指,所以新的对象也可以使用constructor这个属性

function Rabbit() {}
console.log(new Rabbit.constructor())
let obj = {}
alert(obj) 

显然obj用了toString(),但是哪来的?它的[[Prototype]]是Object, 所以用了上面的方法

借用原型
let obj = {
    0: 'hello',
    1: 'a',
    length: 2
}
obj.join = Array.prototype.join
console.log(obj.join(','))

也可以直接obj.prototype = Array.prototype,但是只能继承一个原型

与原型有关的方法
Object.create()
第一个参数是原型,第二个是属性描述符

let obj = Object.create(null)
一个plain对象,什么都没有继承,原型是null
Object.getPrototypeOf(obj)//得到prototype
Object.setPrototypeOf(obj, proto) //设置prototype

class MyClass {
    constructor(name) {
        this.name = name
    }
    
    sayHi() {
        console.log(this.name)
    }
}

let user = new MyClass('a')
user.sayHi()

当new的时候,调用constructor,构造对象

class本质上是一个Function, 它是个constructor,它的prototype中放置了类的方法如sayHi

类表达式
let User = class MyClass {
    
}
let user = new User()
getter/setter
class User {
    constructor(name) {
        this.name = name
    }

    get name() {
        return this._name
    }

    set name(val) {
        this._name = val
    }
}
class字段
class User {
    age = 20;
    constructor(name) {
        this.name = name
    }

    get name() {
        return this._name
    }

    set name(val) {
        this._name = val
    }
}

类字段不在prototype上,而是在每个对象上

类继承

class Animal {
    constructor() {

    }

    sayHi() {
        console.log('###')
    }
}

class Cat extends Animal {
    constructor() {
        super()
    }

    sayHi() {
        super.sayHi()
        console.log('***')
    }
}


let cat = new Cat()
cat.sayHi()

constructor中要调用super

本质上就是两个构造函数,将它们的prototype用原型链串起来 在js中,还可以extends 函数

我们在new的时候,创建一个空对象,并赋值给this 但是继承用的父类的constructor,它期望父类完成相关工作,所以必须要有super

重写类字段
class Animal {
  name = 'animal';

  constructor() {
    alert(this.name); // (*)
  }
}

class Rabbit extends Animal {
  name = 'rabbit';
}

new Animal(); // animal
new Rabbit(); // animal

父类的类字段在super前创建,子类在之后所以会如此

[[HomeObject]]

我们可以简单尝试实现一下类

let animal = {
    name: 'Animal',
    eat() {
        console.log(this.name)
    }
}

let rabbit = {
    name: 'Rabbit',
    __proto__: animal,
    eat() {
        this.__proto__.eat.call(this)
    }
}

rabbit.eat()

但是,如果再来一层继承?

let animal = {
  name: "Animal",
  eat() {
    alert(`${this.name} eats.`);
  }
};

let rabbit = {
  __proto__: animal,
  eat() {
    // ...bounce around rabbit-style and call parent (animal) method
    this.__proto__.eat.call(this); // (*)
  }
};

let longEar = {
  __proto__: rabbit,
  eat() {
    // ...do something with long ears and call parent (rabbit) method
    this.__proto__.eat.call(this); // (**)
  }
};

longEar.eat();

不可以,longEar.eat()实际上执行longEar.proto.eat.call(longEar), 就是rabbit.eat.call(longEar),就是longEar.__proto__eat.call(longEar),又回来了,循环引用,然后爆栈了

所以使用了[[HomeObject]] 因此,在类的方法中加入了[[HomeObject]],他是这个对象,因此在super的时候我们就知道该做什么了

静态属性和方法

class User {
    static staticMethod() {
        console.log('b')
    }

    static name = 'a'
}

console.log(User.name)
User.staticMethod()

静态的可以继承, extends时就实现了,是将两个构造函数用prototype串联起来

受保护的字段

这并非js语言级别的,它并没有被实现,只是一种大家的约定 _name这就是保护字段,你不能直接访问它

class CoffeeMachine {
    _waterAmount = 0;
    set waterAmount(value) {
        this._waterAmount = value
    }
    get waterAmount() {
        return this._waterAmount
    }
    constructor(power) {
        this._power = power
    }
}

let coffeeMachine = new CoffeeMachine(100)

coffeeMachine.waterAmount = 10
console.log(coffeeMachine.waterAmount)

私有属性和方法

私有属性以#为前缀

class CoffeeMachine {
    #waterLimit = 200;

    #fixWaterAmount(value) {
        return this.#waterLimit
    }

    setWaterAmount(value) {
        this.#waterLimit = this.#fixWaterAmount(value)
    }
}

let coffeeMachine = new CoffeeMachine()

现在这些#我们无法从外部访问 他们无法直接继承,只能用get/set操作

内建类

class PowerArray extends Array {
  isEmpty() {
    return this.length === 0;
  }
}

let arr = new PowerArray(1, 2, 5, 10, 50);
alert(arr.isEmpty()); // false

let filteredArr = arr.filter(item => item >= 10);
alert(filteredArr); // 10, 50
alert(filteredArr.isEmpty()); 

filter返回的数组是PowerArray,如果想返回Array,可以

class PowerArray extends Array {
  isEmpty() {
    return this.length === 0;
  }

  // 内建方法将使用这个作为 constructor
  static get [Symbol.species]() {
    return Array;
  }
}

内建类没有方法继承, 如Object和Date

instanceof

这个操作符判断对象是否属于某个class, 包括继承

class Animal {
    static [Symbol.hasInstance](obj) {
        if (obj.canEat) return true
    }
}

let obj = { canEat: true }
console.log(obj instanceof Animal)

如果类有静态方法Symbol.hasInstance则调用它

如果没有,就判断是否在原型链上

相关方法

objA.isPrototypeOf(objB)判断是否在原型链上,可以Class.prototype.isPrototypeOf(obj)

Symbol.toStringTag
let user = {
    [Symbol.toStringTag]: 'user'
}

这个属性定义对象的toString行为 很多内部对象都有

Mixin模式

因为通过原型链,只能单继承,所以需要mixin帮助

let sayHiMixin = {
    sayHi() {
      alert(`Hello ${this.name}`);
    },
    sayBye() {
      alert(`Bye ${this.name}`);
    }
  };
  
  // 用法:
  class User {
    constructor(name) {
      this.name = name;
    }
  }
Object.assign(User.prototype, sayHiMixin)
new User('d').sayHi()

mixin的对象再继承

let sayMixin = {
  say(phrase) {
    alert(phrase);
  }
};

let sayHiMixin = {
  __proto__: sayMixin, // (或者,我们可以在这儿使用 Object.setPrototypeOf 来设置原型)

  sayHi() {
    // 调用父类方法
    super.say(`Hello ${this.name}`); // (*)
  },
  sayBye() {
    super.say(`Bye ${this.name}`); // (*)
  }
};

class User {
  constructor(name) {
    this.name = name;
  }
}

// 拷贝方法
Object.assign(User.prototype, sayHiMixin);

// 现在 User 可以打招呼了
new User("Dude").sayHi(); // Hello Dude!

sayHiMixin中的super,是通过mixin原型查找方法而不是class,因为super中的[[HomeObject]]就是mixin原型

方法sayHi中的[[HomeObject]]内部引用的是sayHiMixin, 当super在[[HomeObject]].[[Prototype]]中寻找父方法时,搜索的是sayHiMixin.[[Prototype]]

他们可以这样用

转换可以返回任意原始类型

这是我用vue3实现的饿了么,如果您感兴趣,希望您能看看,谢谢 github.com/goddessIU/v…

项目预览地址 goddessiu.github.io/