# JS 进阶小记

# Class

# 静态属性

  • class 方式定义
class Husky{
  static name = 'husky'
}
1
2
3
  • function 方式定义
function Husky(){}
Husky.name = 'husky'
1
2

# 静态方法

  • class 方式定义
class Husky{
  static getName=function(){}
}
1
2
3
  • function 方式定义
// 一下两种方式都可以
function Husky(){}
Husky.getName=function(){}
Husky.__proto__.getAge=function(){}
1
2
3
4

# 静态方法实用小案例

  • 通过静态方法创建对象并传递参数
class Husky{
  constructor (name, age){
    this.name = name
    this.age = age
  }
  static init(...args){
    return new this(...args)
  }
}
let obj = Husky.init('husky', 1)
console.log(obj.name)
// husky
1
2
3
4
5
6
7
8
9
10
11
12

# 访问器

  • 通过 get set设置类的访问器
class Husky{
  constructor(name){
    this._name = name
  }
  get name(){
    return this._name
  }
  set name(name){
    if(/\w+/.test(name)) throw new Error('名字不能包含特殊符号')
    this._name = name
  }
}
let husky = new Husky('twohaha')
husky.name = 'smalltwohaha'
console.log(husky.name)
// smalltwohaha
husky.name = 'smalltwohaha@'
console.log(husky.name)
// VM1874:9 Uncaught Error: 名字不能包含特殊符号
// at Husky.set name [as name] (<anonymous>:9:32)
// at <anonymous>:14:12
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 使用Symbol来定义受保护的属性

  • 使用Symbol定义class中的私有属性,这个class和继承了这个class的class可以使用这个私有属性
const protecteds = Symbol()
class Person{
  constructor(){
    this[protecteds] = {}
    this[protecteds].type = 'humans' 
  }
}

class Student extends Person{
  constructor(){
    super()
    console.log(this[protecteds].type)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • 这个时候直接通过.protecteds赋值和使用这个值就访问不到了,可以通过getset访问器的形式修改这个值q

# 使用WeakMap来定义受保护的属性

  • 这个方法要配合模块化来使用,否则没有效果
const protecteds = new WeakMap()
class Person{
  constructor(){
    protecteds.set(this,{
      ...protecteds, type:'humans'
    }) 
  }
  get type(){
    return protecteds.get(this).type
  }
  set type(value){
    protecteds.set(this,{...protecteds,type:value})
  }
}
let humans = new Person()
console.log(humans.type)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 使用 # 定义私有属性

  • 使用#定义私有属性

class Person{
  #type='humans'
  get type(){
    return this.#type
  }
  set type(value){
    this.#type = value
  }
}
let person = new Person()
console.log(person.type)
1
2
3
4
5
6
7
8
9
10
11
12

# super()解决的问题

  • 原型攀升,防止this指向混乱

let basic = {
  name:'basic',
  getName(){
    console.log(this.name)
  }
}
let husky = {
   __proto__: basic,
  name:'husky',
  getName(){
    // console.log(this.__proto__.getName.call(this))
    // 此时的this指的是子类twohaha,所以他继续调用husky类的getName方法,进入死循环
    super().getName()
  }
}

let twohaha = {
  __proto__: husky,
  name: 'twohaha',
  getName(){
    // console.log(this.__proto__.getName.call(this))
    // 使用上面方式只能实现父子结构继承,不能更多了,防止死循环
    // 所以引出了super关键字来做原型攀升,避免this指向混乱
    super().getName()
  }
}
twohaha.getName()
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
26
27
28
  • 在子类中执行super()执行的是父类的constructor方法
  • 可以使用super.getName()的方式调用父类方法

# instanceof 检测对象的实现

  • 该方法还可以检测原型链中的继承实现
class Basic{}
class Husky extends Basic{}
let husky = new Husky()
console.log(husky instanceof Husky) 
// true
console.log(husky instanceof Basic) 
// true
1
2
3
4
5
6
7

# 对象

# 深拷贝

  • 1.简单深拷贝,特殊情况暂不考虑
function deepCopy(obj){
  let res = obj instanceof Array ? [] : {}
  for(let [key,value] of Object.entries(obj)){
    res[key] = typeof value === 'object' ? deepCopy(value) : value
  }
  return res
}
1
2
3
4
5
6
7

# 工厂函数,创建一类对象

  • 定义好工厂函数后就可以,定量生产对象了
function dog (type, name) {
  return {
    type,
    name,
    info () {
      console.log(`品种:${this.type},名字:${this.name}`)
    }
  }
}

let twohaha = dog('husky', 'twohaha')
twohaha.info()
// 品种:husky,名字:twohaha

let keji = dog('keji', 'keke')
keji.info()
//  品种:keji,名字:keke
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 利用闭包,防止外部改变内部变量

function Dog (type, name) {
  let data = { type, name }
  let getType = function () {
    return data.type
  }
  this.getInfo = function () {
    return getType()
  }
}

let twohaha = new Dog('husky', 'twohaha')
twohaha.getType = function(){
  return 'keji'
}
console.log(twohaha.getInfo())
// husky
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 属性特征

# 获取对象的某个属性特征的方法 Object.getOwnPropertyDescriptor(Object,'attribute')

let husky ={
  name: 'husky',
  age: 18
}
console.log(Object.getOwnPropertyDescriptor(husky,'name'))
// {value: "husky", writable: true, enumerable: true, configurable: true}
1
2
3
4
5
6

# 获取对象的全部属性特征的方法 Object.getOwnPropertyDescriptors(Object)

# 灵活配置属性特征 Object.defineProperty(Object,'attribute',{配置})

  • writable : 是否可重新赋值
  • enumerable : 是否可遍历
  • configurable : 是否可删除或者是否可重新配置
  • 给对象的属性writable设置为false
let husky ={
  name: 'husky',
  age: 18
}
Object.defineProperty(husky,'name',{
  writable:false
})
husky.name = 'keji'
console.log(husky.name)
// husky
1
2
3
4
5
6
7
8
9
10
  • 给对象的属性enumerable设置为false
let husky ={
  name: 'husky',
  age: 18
}
Object.defineProperty(husky,'name',{
  enumerable:false
})
console.log(Object.keys(husky))
// ["age"]
1
2
3
4
5
6
7
8
9
  • 给对象的属性configurable设置为false
let husky ={
  name: 'husky',
  age: 18
}
Object.defineProperty(husky,'name',{
  configurable:false
})
// 如果在对husky对象的name属性进行配置,就会报错
// Object.defineProperty(husky,'name',{
//   configurable:true
// })
delete husky.name // 删除失败
console.log(husky)
// {name: "husky", age: 18}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 灵活配置属性特征 Object.defineProperties(Object,'attribute',{批量配置})

# 禁止向对象添加新属性

  • 通过preventExtensions的处理,对象不能在新增属性了
let husky ={
  name: 'husky',
  age: 18
}
Object.preventExtensions(husky)
husky.sex = 'male'
console.log(husky)
// {name: "husky", age: 18}
1
2
3
4
5
6
7
8

# 判断是否可以添加属性

  • 通过isExtensible来判断对象是否可以添加属性
let husky ={
  name: 'husky',
  age: 18
}
Object.preventExtensions(husky)
if(Object.isExtensible(husky)){
  husky.sex = 'male'
}
console.log(husky)
// {name: "husky", age: 18}
1
2
3
4
5
6
7
8
9
10

# 封闭对象 seal 和 isSealed

  • 通过seal来封闭对象,不能新增属性,不可删除,不可重新配置对象特征 但是 可以重新对已有的对象属性赋值
  • isSealed来判断该对象是否封闭
let husky ={
  name: 'husky',
  age: 18
}
Object.seal(husky)
console.log(Object.isSealed(husky))
// true 证明该对象已经封闭
Object.defineProperty(husky,'name',{
  writable: true
})
husky.sex = 'male'
husky.name = 'keji'
delete husky.name
console.log(husky)
// {name: "keji", age: 18}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 冻结对象 freeze 、isFrozen

  • 冻结对象 比 seal还要强,连对已有的对象属性重新赋值都不可以,只可以读取使用,和遍历
  • isFrozen 判断对象是否被冻结
let husky ={
  name: 'husky',
  age: 18
}
Object.freeze(husky)
console.log(Object.isFrozen(husky))
// true 证明该对象已经被冻结
husky.sex = 'male'
delete husky.name
console.log(husky)
// {name: "husky", age: 18}
1
2
3
4
5
6
7
8
9
10
11

# 访问器 set get

  • 通过set给对象的属性赋值,可以保证数据的质量
  • get可以对原始数据进行加工
let husky ={
  data:{
    name: 'husky',
    age: 2,
  },
  set age(value){
    if(typeof value !== 'number' || value > 300 || value < 0){
      throw new Error('请赋值正确的年龄')
    }
    this.data.age = value
  },
  get age(){
    return this.data.age
  }
}
husky.age = 10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 小案例,通过get,set完成本地存储

let sessionStorage ={
  set userInfo(value){
    sessionStorage.setItem('userInfo',JSON.stringfy(value))
  },
  get userInfo(){
    let userInfo = sessionStorage.get('userInfo')
    if(userInfo){
      return userInfo
    }else{
      // 未登录重新登录
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# Proxy 代理

# 基本语法

let husky ={
  name: 'husky'
}
let proxy = new Proxy(husky,{
  get(obj,property){
    return obj[property]
  },
  set(obj, property, value){
    obj[property] = value
    return true
  }
})
proxy.name = 'keji'
console.log(husky)
// {name: "keji"}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 不仅可以代理对象,还可以代理函数

function husky(value){
  console.log(value * value)
}

let proxy = new Proxy(husky,{
  apply(func, obj, args){
    console.log('property is'+args)
    func.apply(this, args)
  }
})
proxy.apply(null,[100])
// property is100
// 10000
1
2
3
4
5
6
7
8
9
10
11
12
13

# Proxy 对数组进行代理

let husky = ['twohaha', 'keji', 'goldmao']
let proxy = new Proxy(husky,{
  get(array, index){
    return array[index].toUpperCase()
  }
})
console.log(proxy[0])
// TWOHAHA
1
2
3
4
5
6
7
8

# Vue3双向数据绑定原理

function View(){
  let proxy = new Proxy({},{
    get(obj, property){},
    set(obj, property, value){
      document.querySelectAll(`[v-model="${property}"]`).
      forEach(item=>{
        item.value = value
      })
    }
  })
  this.init = function(){
    const els = document.querySelectorAll("[v-model]");
    els.forEach(item => {
      item.addEventListener("keyup", function(){
        proxy[this.getAttribute("v-model")] = this.value
      })
    })
  }
}

new View().init()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# JSON

# toJSON自定义序列化

  • 可以通过给对象怎家toJSON属性,自定义JSON.stringify的序列化数据
let params = {
  name:'🐰哈哈',
  id:1,
  type:'husky',
  shortName:'2ha',
  englishName:'twohaha',
  toJSON:function(){
    return [
      ...Object.keys(this)
    ]
  }
}
console.log(JSON.stringify(params))
// ["name","id","type","shortName","englishName","toJSON"]
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# JSON.parse自定义解析

let husky = `{"name":"🐰哈哈","id":1,"type":"husky","shortName":"2ha","englishName":"twohaha"}`
let keys = []
let res = JSON.parse(husky,(key, value)=>{
  // others logic
  return value
})
console.log(res)
// {name: "🐰哈哈", id: 1, type: "husky", shortName: "2ha", englishName: "twohaha"}
1
2
3
4
5
6
7
8

# 函数进阶

# 箭头函数中的this指向

  • 箭头函数中的this指向的是上下文
let husky = {
  name:'twohaha',
  getName :function () {
    return  ()=> {
      return this.name
    }
  }
}
console.log(husky.getName()()) // twohaha
1
2
3
4
5
6
7
8
9
  • 如果上面的例子不用箭头函数
let husky = {
  name:'twohaha',
  getName :function () {
    return  function() {
      return this.name
    }
  }
}
console.log(husky.getName()()) // undefined
1
2
3
4
5
6
7
8
9
  • 因为此时的this指向的不再是husky对象,而是window,所以会输出undefined

# call 和 apply

  • 可以改变this指向,apply的参数要求是数组,call正常传参数
function Husky(){
  this.name = name
  this.getName=function(type){
    return this.name + ',品种:' + type
  }
}
let keji = new Husky('keji')
keji.getName.call({name:'twohaha'},'husky')
// "twohaha,品种:husky"

keji.getName.apply({name:'twohaha'},['husky'])
// "twohaha,品种:husky"
1
2
3
4
5
6
7
8
9
10
11
12

# bind

  • 同样可以改变this指向,返回一个函数
function Husky(){
  this.name = name
  this.getName=function(type){
    return this.name + ',品种:' + type
  }
}
let keji = new Husky('keji');
let twohaha = keji.getName.bind({name:'twohaha'})
twohaha('哈士奇')
// "twohaha,品种:哈士奇"
1
2
3
4
5
6
7
8
9
10
  • 这里有一点需要注意,如果我们在用bind的时候直接传了参数,那么之后在传参数就不起作用了,如果在bind的时候没传参数,那么后面调用该函数的时候传参数是有效果的

let twohaha = keji.getName.bind({name:'twohaha'},'husky')
twohaha('哈士奇') 
// "twohaha,品种:husky"
1
2
3
4
Last Updated: 1/23/2022, 10:16:22 AM