js知识点总结三

115 阅读9分钟

Set

set是es6新增的一种数据结构,能够自动去重,可以使用在数组去重里

let set = new Set([1,2,3,4,5, 5])
let arr = [...set] // [1,2,3,4,5]

set可以循环访问数据

let set = new Set([1,2,3,4,5, 5])
set.forEach((item) => {
  console.log(item) 
})

set的add, has

let set = new Set([1,2,3,4,5, 5])
set.add(7) // 添加数据
set.has(7) // true 判断是元素是否存在

set实现数组的并/交/差

let arr1 = [1,2,3]
let arr2 = [2,3,4]
let res = []
// -----并集-------
res = [...new Set([...arr1, ...arr2])] // [1,2,3,4]
// -----交集-------
let set1 = new Set(arr1)
res = arr2.filter((item) => {
  return  set1.has(item)
})
// -----差集-------
let set2 = new Set(arr2)
res = arr1.filter((item) => {
  return  !set2.has(item)
})

Map

Map是es6新增加的数据结构,类似对象,但是Map可以设置任意的key,es5里面的对象,在设置key的时候,如果把一个对象作为另一个对象的key,这个对象key会自动调用toString()变成一个字符串,就会有key值覆盖的问题。

Map和Object的区别

es6对象设置为key,值相同的对象会覆盖

let obj = {}
let obj1 = {}
let obj2 = {}
obj[obj1] = 'qq'
obj[obj2] = 'ss'
console.log(obj)  // { '[object Object]': 'ss' }


let obj = new Map()
let obj1 = {}
let obj2 = {}
obj.set(obj1, 'qq')
obj.set(obj2, 'ss')
console.log(obj)  // Map(2) { {} => 'qq', {} => 'ss' }

Map的方法

  • set设置属性
  • get获取属性
  • has判断属性是否存在
  • delete删除属性
  • keys遍历key
  • values遍历值
  • entries遍历键值对
    let arr = [
    ['name','章三'],
    ['age', 12],
    ['sex', '男']
    ]
    
    let map = new Map(arr)
    
    map.get('name')
    map.set('like', '看书')
    map.has('age')
    map.delete('like')
    //-----------------------
    // keys() 返回一个引用的迭代器对象
    // values() 返回一个新的迭代器对象
    // entries() 返回一个新的迭代器对象
    let keyObj = map.keys() // [Map Iterator] { 'name', 'age', 'sex' } 
    let valObj = map.values() // [Map Iterator] { '章三', 12, '男' }
    let keyValObj = map.entries()
    

Map的参数

Map 可以接受数组作为参数,但是数组里面的元素,必须是以键值对数组形式存在

let items = [
  ['name', 'zhangsan'],
  [{'name': 'lisi'}, 'wangwu']
]

let map = new Map(items) 
console.log(map.get('name')) // zhangsan
console.log(map.get({'name': 'lisi'})) // undefined

上面map.get({'name': 'lisi'})undefined应为在使用map.get的时候,已经重新定义了一个对象这个对象和原来数组里面的对象,已经不是一个地址了

let obj = {'name': 'lisi'}
let items = [
  ['name', 'zhangsan'],
  [obj, 'wangwu']
]

let map = new Map(items) 
console.log(map.get('name')) // zhangsan
console.log(map.get(obj)) // wangwu

Map接受数组的本质

let obj = {'name': 'lisi'}
let items = [
  ['name', 'zhangsan'],
  [obj, 'wangwu']
]

let map = new Map()
items.forEach(([key,val]) => {
  map.set(key, val)
})

Map 的参数,只要符合参数的数据结构有Iterator接口,并且数组的每一个元素都是双元素的

let set = new Set([
  ['name', '章三'],
  ['age', 12]
])
let map = new Map(set)
console.log(map.get('name'))  // 章三
console.log(map.get('age'))  // 12

Interator和for...of

所有能使用for...of的数据结构,都具有Interator迭代器。Interator是为了能够为不同的数据结构提供统一的访问接口。

  • Interator迭代器是一个函数
  • 函数会返回指针对象
  • 对象含有一个next属性
  • next也是一个函数
  • next函数返回一个对象{value: 'xx', done: true/false}
  • done为false迭代继续
  • done为true的时候迭代结束
function makeInterator(arr) {
  let index = 0
  return {
    next: function() {
      
      if (index < arr.length) {
        return {
          value: arr[index++],
          done: false
        }
      } else {
        return {
          value: undefined,
          done: true
        }
      }
    }
  }
}

let it = makeInterator(['a', 'b', 'c'])
console.log(it.next()) // { value: 'a', done: false }
console.log(it.next()) // { value: 'b', done: false }
console.log(it.next()) // { value: 'c', done: false }
console.log(it.next()) // { value: undefined, done: true }

Object对象的属性上没有迭代器,自已可以改造一个,让对象使用for...of

let obj = {
  data: ['hello', 'world']
}

obj[Symbol.iterator] = function() {
  let data = this.data;
  let index = 0;
  return {
    next: function() {
      if (index < data.length) {
        return {
          value: data[index++],
          done: false
        }
      } else {
        return {
          value: undefined,
          done: true
        }
      }
    }
  }
}

for(let item of obj) {
  console.log(item)
}
console.log(...obj) // hello world
let obj = {
  data: ['hello', 'world'],
  data1: ['1', '2']
}

obj[Symbol.iterator] = function() {
  let keys = Object.keys(this)
  let index = 0;
  let _this = this
  return {
    next: function() {
      if (index < keys.length) {
        let key = keys[index]
        index++
        return {
          value: _this[key],
          done: false
        }
      } else {
        return {
          value: undefined,
          done: true
        }
      }
    }
  }
}
console.log(...obj) // [ 'hello', 'world' ] [ '1', '2' ]

for in和for of的区别

  • for in 用来遍历对象的key,数组的key就是下标索引,如果给数组增加一个.a的属性,for in也是会遍历出来的,这样就不是期望的了,所以for in 不适合遍历数组
  • for of上面已经详细的讲过了,他很适合遍历数组

箭头函数和this

箭头函数和普通函数的区别

  • 箭头函数没有arguments,可以通过rest来获取参数

      function fun1() {
        console.log(arguments)
      }
      const fun = (...arg) => {
        console.log(...arg)
      }
      fun1(1,2,3)
      fun(4,5,6)
    

    image.png

  • 箭头函数自己没有this,内部使用的this是从作用域链的上一层继承来的,箭头函数的this一直指向函数定义的地方

  • var name = '里斯'
    let obj = {
      name: '张三',
      fun: function() {
        return {
          fn1: () => {
            console.log(this.name)
          }
        }
      },
      fn2: () => {
        console.log(this.name)
      }
    }
    
    obj.fun().fn1() // 张三
    obj.fn2() // 里斯
    
- 箭头函数不能使用call,apply,bind来改变this指向
```js
  let obj = {
    name: '张三',
  }
  var name = '顽固'
  function fn1() {
    console.log(this.name)
  }
  const fn2 = () => {
    console.log(this.name)
  }

  fn1.call(obj) // 张三
  fn2.call(obj) // 顽固
    
  • 箭头函数不能作为构造函数
  function fn1() {
      
    }
    const fn2 = () => {
      
    }
    const fn1Obj = new fn1()
    const fn2Obj = new fn2() // n2 is not a constructor

    console.log(fn1Obj)
  • 箭头函数没有原型

     const fn2 = () => {}
    console.log(fn2.prototype) // undefined
    
  • 箭头函数不能使用yeild,不能作为Generator函数

一个小题

 var name = 'window'
  var obj = {
    name: 'obj',
    methods: () => {
      console.log(this.name)
    },
    fn: function (cb) {
      cb()
    }
  }
  obj.fn1 = function () {
    obj.fn(() => { console.log(this.name) })
  }
  var fn1 = obj.fn1 // 普通函数
  /**
   * obj.methods() 是一个箭头函数,他要去作用域链的上一层查找this,obj对象是一个对象,作用域只有全局和函数2中,对象自己并没有作用域,所以obj.methods()的this指向全局
   */
  obj.methods() // window
  /**
   * obj.fn(() => { console.log(this.name) }) 中obj.fn接受一个函数作为参数,这个参数函数其实是在全局里面定义的,不是在obj.fn()里面定义的,箭头函数的this永远指向函数定义的地方,所以this指向window
   */
  obj.fn(() => { console.log(this.name) }) // window
  /**
   * var fn1 = obj.fn1 这个代码已经把obj上的fn1赋值给了fn1,fn1是一个在全局中的函数,this已经指向window,已经和obj没有关系了
   */
  fn1() // window
  /**
   * 这里调用obj.fn1(), fn1函数是普通函数,所以fn1的this指向obj,在obj.fn1()里面调用箭头函数,箭头函数的上一级自然是obj.fn1(),所以this自然是obj
   */
  obj.fn1() // obj

promise

promise有3种状态,状态一旦改变就不能更改了,promise的返回值也是一个promise

  • pedding初始状态

  • fulfilled成功

  • rejected失败

new Promise((resolve, reject) => {
  resolve(1)
  reject(2)
}).then((res) => {
  console.log(res) 
}).catch((err) => {
  console.log(err)
})

then()接收成功值,catch()接收失败值

promise的常用方法

  • Promise.resolve(1) 返回一个状态为fulfilled的promise对象
  • Promise.reject() 返回一个状态为rejected的promise对象
  • Promise.all() 所有的promise都成功,才能返回结果
  Promise.all([
    new Promise((resolve, reject) => {
      resolve(1)
    }),
    new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(2)
      }, 1000)
    })
  ]).then((res) => {
    console.log(res) // [1,2]
  })
Promise.all([
  new Promise((resolve, reject) => {
    resolve(1)
  }),
  new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(2)
    }, 1000)
  })
]).then((res) => {
  console.log(res)
}).catch((err) => {
  console.log(err) // 2
})
  • Promise.any()返回最先成功的那个promise结果,所有的都失败了就失败
Promise.any([
  new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(2)
    }, 1000)
  }),

  new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('')
    }, 3000)
  }),
  new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('1-1')
    }, 2000)
  }),
  
]).then((res) => {
  console.log('res',res)
}).catch((err) => {
  console.log('err',err)
})
  • Promise.race()永远输出提前执行完毕的结果,不管成功与否,结果不是数组
Promise.race([
  new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(2)
    }, 1000)
  }),

  new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('')
    }, 3000)
  }),
  new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('1-1')
    }, 2000)
  }),
  
]).then((res) => {
  console.log('res',res)
}).catch((err) => {
  console.log('err',err)
})
// err 2
  • Promise.allsettled返回所有promise的状态,结果是数组
Promise.allSettled([
  new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(2)
    }, 1000)
  }),

  new Promise((resolve, reject) => {
    setTimeout(() => {
      reject('3')
    }, 3000)
  }),
  new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('1-1')
    }, 2000)
  }),
  
]).then((res) => {
  console.log('res',res)
}).catch((err) => {
  console.log('err',err)
})
/**
 res [
  { status: 'rejected', reason: 2 },
  { status: 'rejected', reason: '3' },
  { status: 'fulfilled', value: '1-1' }
]
 */

async/await

能够更简单的描写promise的异步行为 async的返回值是一个promise。

  • async的函数内部会返回一个promise,如果不是会被默认转化
async function fn() {
  return '1'
}

console.log(fn()) // Promise { '1' }
  • await接收的promise状态改变之后的值,如果改变之后不是promise,await会把改值变成正常处理的promise(resolve处理的值)
 async function fn() {
  await 1
  
}
console.log(fn())

image.png

  • await和面的promise的状态是reject的话,await后面的代码不会执行,async的函数会返回状态为rejected的promise
  • async函数内部如果存在await,await表达式会暂停整个async函数的执行,等当前位置promise状态改变后才能恢复

小题练习

function fn () {
  return new Promise((resolve) => {
    console.log('Promise1')
    fn1()
    setTimeout(() => {
      console.log('Promise2')
      resolve()
      console.log('Promise3')
    }, 0);
  })
}
async function fn1() {
  var p = Promise.resolve().then(() => {
    console.log('Promise6')
  })
  await p.then(() => {
    console.log('Promise7')
  })
  console.log('end')
}
console.log('script')
setTimeout(() => {
  console.log('setTimeout')
}, 0)
fn().then(() => {
  console.log('Promise4')
})
// 结果
// script
// Promise1
// Promise6
// Promise7
// end
// setTimeout
// Promise2
// Promise3
// Promise4

代码执行的时候顺序

  • console.log('script') 得到 script
  • setTimeout(() => { console.log('setTimeout') }, 0)放入宏任务队列 -fn()调用函数
  • new Promise((resolve) => { console.log('Promise1') fn1() setTimeout(() => { console.log('Promise2') resolve() console.log('Promise3') }, 0); })是在函数调用的内部代码,要同步执行
  • console.log('Promise1')
  • fn1()调用fn1
  • var p = Promise.resolve().then(() => { console.log('Promise6') }) await p.then(() => { console.log('Promise7') }) console.log('end')
  • p = Promise.resolve()是一个微任务,resolve()会触发.then(),所以console.log('Promise6')放入微任务
  • await p.then(() => { console.log('Promise7') })``await的方法必须执行完,后面的代码才能执行,所以这是时候** script, Promise1, Promise6,Promise7,end**都打印出来
  • 微任务执行完毕了,要去执行宏任务了,打印setTimeout, Promise2
  • resolve()触发 fn().then(),fn().then()被放入微任务
  • 继续打印Promise3
  • 最后打印Promise4
async function fn () {
  setTimeout(function () {
    console.log(1)
  }, 0)
  Promise.resolve().then(() => console.log(4))
  await setTimeout(function () {
    console.log(5)
  }, 0)
  await Promise.resolve().then(() => console.log(6))
  Promise.resolve().then(() => console.log(7))
  console.log(3)
}
fn()
/**
 * 4
 * 6
 * 3
 * 7
 * 1
 * 5
 */

proxy

proxy可以为一个对象设置代理,可以实现拦截是自定义方法(赋值,查找,枚举,函数调用...)

proxy语法

  • 对象代理
const data = {}
const handler = {
  get(target, property, receiver) {

  }
}
const p = new Proxy(data, handler)
  • 可以撤销的对象代理
const data = {}
const handler = {
  get(target, property, revicer) {

  }
}
const p = Proxy.revocable(data, handler)

proxy的get会拦截目标对象的以下操作

  • obj.xxx获取对象属性
  • Object.cerate(p).xxx用Object.cerate复制proxy代理的结果,并访问属性
  • Reflect.get() proxy的set会拦截目标对象的以下操作
  • obj.xxx = 'yyy'
  • Object.cerate(p).xxx = 'yyy'
  • Reflect.set()

proxy使用案例

var data = [
  { name: 'Firefox'    , type: 'browser' },
  { name: 'SeaMonkey'  , type: 'browser' },
  { name: 'Thunderbird', type: 'mailer' }
]
// 需要实现的功能
// 1. 通过索引返回对应数据 proxy[0]
// 2. 通过number属性返回数组长度 proxy.number
// 3. 通过name获取对应的数据 proxy['Firefox']
// 4. 通过type返回对应的数据 proxy['browser']
// 5. 通过types返回data中的type products.types
let data = [
  { name: 'Firefox'    , type: 'browser' },
  { name: 'SeaMonkey'  , type: 'browser' },
  { name: 'Thunderbird', type: 'mailer' }
]
let p = new Proxy(data, {
  get(target, prop) {
    if(prop in target) {
      return target[prop]
    }
    if(prop === 'number') {
      return target.length
    }
    let res
    let types = {}
    for(let item of target) {
      if (item.name === prop) {
        res = item
      }
      if (types[item.type]) {
        types[item.type].push(item)
      } else {
        types[item.type] = [item]
      }

    }
    if (res) return res
    if(prop in types) {
      return types[prop]
    }
    if (prop === 'types') {
      return Object.keys(types)
    }
  }
})


console.log(p[0])
console.log(p.number)
console.log(p['Firefox'])
console.log(p['browser'])
console.log(p['types'])

class

class是构造函数的语法糖,需要使用extends来实现继承

class的特点

  • class的数据类型是一个函数
class A {}
console.log(typeof A) // function
  • class原型的constructor指向class
class A {}
console.log(A.prototype.constructor === A) // true
  • new声明的实例的constructor指向当前的class
 class A {}
let a = new A;
console.log(a.constructor === A) // true
  • class内部的方法都是在类的原型上
class A {
  fn() {
    console.log(1)
  }
  fn1() {}
}
let a = new A();
console.log(a)

image.png

  • 通过类创建对象的本质都是在调用constructor,如果没有就在使用时会默认添加
class A {
    }
class A {
  constructor() {
  }
}
// 这2种写法完全是等价的
  • class不能直接调用,需要使用new关键字
class A {
}
A()

image.png

  • class内部使用严格模式,内部使用this的时候,指向的就是class的实例
class Logger {
  printName(name = 'world') {
    this.print(`Hello ${name}`);
  }

  print(text) {
    console.log(text);
  }
}

const logger = new Logger();
const { printName } = logger;
printName()

this的指向被改变了,但是是严格模式,所以this为undefined,如果想正确的访问this,可以使用箭头函数实现

class的用法

  • class有getter和setter
class A {
  get name() {
    return 'name'
  }
  set name(value) {
    console.log('set', value)
  }
}

const a = new A()
a.name = 1  // 'set' 1
console.log(a.name) // 'name'
  • 使用static关键字,声明静态方法 静态方法里面的this,指向类,而不是实例
  • 定义实例的属性
class A {
  a = 1
  b = 'SUCCESS'
  fn() {}
}

const a = new A()
console.log(a)

image.png

  • 子类中的constructor必须使用super,否则报错
 class A {
  a = 1
  b = 'SUCCESS'
  fn() {}
}

class B extends A {
  constructor() {
    // super()
  }
}

const b = new B()
console.log(b)

super关键字,会触发父类的constructor,并且传递参数 image.png

  • 子类继承父类的时候,属性都是在实例上的,但是方法都会放在原型里面
class A {
  a = 1
  b = 'SUCCESS'
  fn() {}
}

class B extends A {
  constructor() {
    super()
  }
}

const b = new B()
console.log(b)

image.png