6.JS-ES6

380 阅读14分钟

Object与Map的区别

Objects 和 Maps 类似的是,它们都允许你按键存取一个值、删除键、检测一个键是否绑定了值。因此(并且也没有其他内建的替代方式了)过去我们一直都把对象当成 Maps 使用。不过 Maps 和 Objects 有一些重要的区别,在下列情况里使用 Map 会是更好的选择:

MapObject
意外的键Map 默认情况不包含任何键。只包含显式插入的键。一个 Object 有一个原型, 原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。备注: 虽然 ES5 开始可以用 Object.create(null) 来创建一个没有原型的对象,但是这种用法不太常见
键的类型一个 Map的键可以是任意值,包括函数、对象或任意基本类型。一个Object 的键必须是一个 String 或是Symbol
键的顺序Map 中的 key 是有序的。因此,当迭代的时候,一个 Map 对象以插入的顺序返回键值。一个 Object 的键是无序的备注: 自ECMAScript 2015规范以来,对象确实保留了字符串和Symbol键的创建顺序; 因此,在只有字符串键的对象上进行迭代将按插入顺序产生键。
Size Map 的键值对个数可以轻易地通过size 属性获取Object 的键值对个数只能手动计算
迭代Map 是 iterable 的,所以可以直接被迭代。迭代一个Object需要以某种方式获取它的键然后才能迭代。
性能在频繁增删键值对的场景下表现更好。在频繁添加和删除键值对的场景下未作出优化。

Set

Set 理解

set类似数组,但是跟数组不一样的就是set里面可以放任何类型,大杂烩一样的东西 比如 [1,'a']

Set 用法 示例【 更多用法 点击示例】

let mySet = new Set();

mySet.add(1); // Set [ 1 ]
mySet.add("some text"); // Set [ 1, "some text" ]
mySet.has(1); // true
mySet.has(3); // false
mySet.size; // 2

mySet.delete(1);  // true,  从set中移除5
mySet.has(1);     // false, 5已经被移除

mySet.size; // 1, 刚刚移除一个值

const numbers = [2,3,4,4,2,3,3,4,4,5,5,6,6,7,5,32,3,4,5]
console.log([...new Set(numbers)])
// [2, 3, 4, 5, 6, 7, 32]

Set属性

  1. get Set[@@species]
  2. Set.prototype.size

Set方法

  1. Set.prototype[@@iterator]()
  2. Set.prototype.add()
  3. Set.prototype.clear()
  4. Set.prototype.delete()
  5. Set.prototype.entries()
  6. Set.prototype.forEach()
  7. Set.prototype.has()
  8. Set.prototype.values()

Map

Map 理解

上面可以点击跳转到MDN,查看详情,下面说的是我自己的理解

map类似对象,不一样的是对象Object的key必须是字符串,而map里面的key和值可以是任何值 比如{1:'1','a':'a',2:2}

Map 用法 示例 【更多用法 点击示例】

let myMap = new Map()
myMap.set('bla','blaa')
myMap.set('bla2','blaa2')
console.log(myMap)  // Map { 'bla' => 'blaa', 'bla2' => 'blaa2' }

myMap.has('bla')    // true
myMap.delete('bla') // true
console.log(myMap)  // Map { 'bla2' => 'blaa2' }

Map属性

  1. get Map[@@species]
  2. Map.prototype[@@toStringTag]
  3. Map.prototype.size

Map方法

  1. Map.prototype[@@iterator]()
  2. Map.prototype.clear()
  3. Map.prototype.delete()
  4. Map.prototype.entries()
  5. Map.prototype.forEach()
  6. Map.prototype.get()
  7. Map.prototype.has()
  8. Map.prototype.keys()
  9. Map.prototype.set()
  10. Map.prototype.values()

★ Object的常用方法有哪些

  1. Object.assign() 【后面的对象的属性覆盖前面的】

  2. Object.create() 【创建一个没有原型的对象,但是这种用法不太常见】

  3. Object.defineProperties()

  4. Object.defineProperty()

  5. Object.entries()【返回键值对数组】

  6. Object.freeze()冻结一个对象。不能被修改】

  7. Object.fromEntries()【把键值对列表转换为一个对象】

  8. Object.getOwnPropertyDescriptor()

  9. Object.getOwnPropertyDescriptors()

  10. Object.getOwnPropertyNames()

  11. Object.getOwnPropertySymbols()

  12. Object.getPrototypeOf()

  13. Object.hasOwn() (en-US)【是否有xxx属性 返回Boolean】

  14. Object.prototype.hasOwnProperty()【是否具有指定的属性】

  15. Object.is()

  16. Object.isExtensible()

  17. Object.isFrozen()

  18. Object.prototype.isPrototypeOf()

  19. Object.isSealed()

  20. Object.keys()

  21. Object.preventExtensions()

  22. Object.prototype.propertyIsEnumerable()

  23. Object.seal()

  24. Object.setPrototypeOf()

  25. Object.prototype.toLocaleString()

  26. Object.prototype.toSource()

  27. Object.prototype.toString()

  28. Object.prototype.valueOf()

  29. Object.values()【属性值的数组】


★ Object.keys

参数

obj: 要返回其枚举自身属性的对象。

返回值

一个表示给定对象的所有可枚举属性的字符串数组。

描述

Object.keys 返回一个所有元素为字符串的数组,其元素来自于从给定的object上面可直接枚举的属性。这些属性的顺序与手动遍历该对象属性时的一致。

例子

// 传入字符串,返回索引
var str = 'ab1234';  
console.log(Object.keys(obj)); //[0,1,2,3,4,5]


// simple array 数组 返回索引
var arr = ['a', 'b', 'c'];
console.log(Object.keys(arr)); // console: ['0', '1', '2']


// array like object
var obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.keys(obj)); // console: ['0', '1', '2']


0-9是数字的顺序,a-z是定义的顺序
// array like object with random key ordering !!!
var anObj = {c: 'ccccc', 2: '22222', 1: '11111', b: 'bbbb', a: 'aaa'};
console.log(Object.keys(anObj)); // console: ['1', '2', 'c', 'b', 'a']
console.log(Object.values(anObj)); // console: ['a', 'b', 'c', 'd', 'aaa']


// getFoo is a property which isn't enumerable
var myObj = Object.create({}, {
  getFoo: {
    value: function () { return this.foo; }
  }
});
myObj.foo = 1;
console.log(Object.keys(myObj)); // console: ['foo']


// 构造函数 返回空数组或者属性名
function Pasta(name, age, gender) {
     this.name = name;
     this.age = age;
     this.gender = gender;
     this.toString = function () {
          return (this.name + ", " + this.age + ", " + this.gender);
     }
}
console.log(Object.keys(Pasta)); //console: []
var spaghetti = new Pasta("Tom", 20, "male");
console.log(Object.keys(spaghetti)); //console: ["name", "age", "gender", "toString"]

返回值顺序

Object.keys在内部会根据属性名key的类型进行不同的排序逻辑。分三种情况:

  1. 如果属性名的类型是Number,那么Object.keys返回值是按照key从小到大排序
  1. 如果属性名的类型是String,那么Object.keys返回值是按照属性被创建的时间升序排序。
  1. 如果属性名的类型是Symbol,那么逻辑同String相同

★ map, filter, reduce

map

map 作用是生成一个新数组,遍历原数组,将每个元素拿出来做一些变换然后放入到新的数组中。

[1, 2, 3].map(v => v + 1) // -> [2, 3, 4]

另外 map 的回调函数接受三个参数,分别是当前索引元素,索引,原数组

console.log([1, 2, 3].map(v => v + 1))  // [2, 3, 4]
console.log(['1','2','3'].map(parseInt)) // [1, NaN, NaN]

第一轮遍历 `parseInt('1', 0) -> 1`
第二轮遍历 `parseInt('2', 1) -> NaN`
第三轮遍历 `parseInt('3', 2) -> NaN`

parseInt

parseInt() 函数解析一个字符串参数并返回一个指定的整数
parseInt(string, radix)
// radix  基数 进制
console.log( parseInt('1', 0)) // 1
console.log( parseInt('2', 3)) // 2
console.log( parseInt('2', 1)) // NaN
console.log( parseInt('02', 10)) // 2

filter

filter 的作用也是生成一个新数组,在遍历数组的时候将返回值为 true 的元素放入新数组,我们可以利用这个函数删除一些不需要的元素

let array = [1, 2, 4, 6]
let newArray = array.filter(item => item !== 6)
console.log(newArray) // [1, 2, 4]

和 map 一样,filter 的回调函数也接受三个参数,用处也相同。

reduce

  1. 作用:

reduce可以将数组中的元素通过回调函数最终转换为一个值

  1. 写法:

如果我们想实现一个功能将函数里的元素全部相加得到一个值,可能会这样写代码

const arr = [1, 2, 3]
let total = 0
for (let i = 0; i < arr.length; i++) {
  total += arr[i]
}
console.log(total) //6 

但是如果我们使用 reduce 的话就可以将遍历部分的代码优化为一行代码

const arr = [1, 2, 3]
const sum = arr.reduce((acc, current) => acc + current, 0)
console.log(sum)
  1. 参数:
  • 参数1:回调函数【(acc, current) => acc + current】
  • 参数2:初始值【0】

4.过程

  • 首先初始值为 0,该值会在执行第一次回调函数时作为第一个参数传入
  • 回调函数接受四个参数,分别为累计值、当前元素、当前索引、原数组,后三者想必大家都可以明白作用,这里着重分析第一个参数
  • 在一次执行回调函数时,当前值和初始值相加得出结果 1,该结果会在第二次执行回调函数时当做第一个参数传入
  • 所以在第二次执行回调函数时,相加的值就分别是 1 和 2,以此类推,循环结束后得到结果 6
  1. 实现map函数示例

想必通过以上的解析大家应该明白 reduce 是如何通过回调函数将所有元素最终转换为一个值的,当然 reduce 还可以实现很多功能,接下来我们就通过 reduce 来实现 map 函数

const arr = [1, 2, 3]
const mapArray = arr.map(value => value * 2)
const reduceArray = arr.reduce((acc, current) => {
  acc.push(current * 2)
  return acc
}, [])
console.log(mapArray, reduceArray) // [2, 4, 6]

如果你对这个实现还有困惑的话,可以根据上一步的解析步骤来分析过程。


★ Proxy

涉及面试题:Proxy 可以实现什么功能?

  • 在 Vue3.0 中通过 Proxy 来替换原本的 Object.defineProperty 来实现数据响应式。

  • Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。

let p = new Proxy(target, handler)

// target 代表需要添加代理的对象,
// handler 用来自定义对象中的操作,比如可以用来自定义 set 或者 get 函数。
  • 接下来我们通过 Proxy 来实现一个数据响应式
let onWatch = (obj, setBind, getLogger) => {
  console.log('obj',obj)
  let handler = {
    get(target, property, receiver) {
      console.log('target-',target) // {a: 2}
      console.log('property-',property) //a
      console.log('receiver-',receiver) //Proxy {a: 2} receiver是this
      getLogger(target, property)
      return Reflect.get(target, property, receiver)
    },
    set(target, property, value, receiver) {
      console.log('target',target) //{a: 1}
      console.log('property',property) //a
      console.log('value',value) //1
      console.log('receiver',receiver) //Proxy {a: 1}  receiver是this
      setBind(value, property)
      return Reflect.set(target, property, value)
    }
  }
  return new Proxy(obj, handler)
}

let obj = { a: 1 }
let p = onWatch(
  obj,
  (v, property) => {
    console.log(`监听到属性${property}改变为${v}`)
  },
  (target, property) => {
    console.log(`'${property}' = ${target[property]}`)
  }
)
p.a = 2 // 监听到属性a改变  // 执行set方法
p.a // 'a' = 2


// Reflect.set() 在一个对象上设置一个属性。
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect/set
var obj1 = {};
Reflect.set(obj1, "prop", "value"); // true
obj1.prop; // "value"

image.png

在上述代码中,我们通过自定义 set 和 get 函数的方式,在原本的逻辑中插入了我们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。

当然这是简单版的响应式实现,如果需要实现一个 Vue 中的响应式,需要我们在 get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要使用 Proxy 替换原本的 API 原因在于 Proxy无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是Proxy可以完美监听到任何方式的数据改变,唯一缺陷可能就是浏览器的兼容性不好了。


★ 继承【小测】


★ 模块化 【】

常见面试题:1.为什么要使用模块化?2.都有哪几种方式可以实现模块化,3.各有什么特点?

立即执行函数

早期

AMD 和 CMD

AMD和CMD现在比较少见,使用方法就是define关键字,里面引用其他的js文件

// AMD
define(['./a', './b'], function(a, b) {
  // 加载模块完毕可以使用
  a.do()
  b.do()
})
// CMD
define(function(require, exports, module) {
  // 加载模块
  // 可以把 require 写在函数体的任意地方实现延迟加载
  var a = require('./a')
  a.doSomething()
})

CommonJS【exports 或者 module.exports 和 require】【node、webpack、后端】【动态导入、同步导入会卡主主线程但是影响不大】

CommonJS 最早是 Node 在使用,目前也仍然广泛使用,比如在 Webpack 中你就能见到它,

exports  = module.exports = {}

exports 是 module.exports的引用

大概就是 var a = {}; var b = a; a 和 b 之间的区别吧

exports 和 module.exports 用法相似但是不能对 exports 直接赋值。因为 var exports = module.exports 这句代码表明了如果直接对 exports 赋值就会导致两者不再指向同一个内存地址,修改并不会对 module.exports 起效。
// a.js
module.exports = {
    a: 1
}
// or 
exports.a = 1

// b.js
var module = require('./a.js')
module.a // -> log 1

ES Module 【ES6的es,所以是前端,浏览器端,是异步导入】

export 或 export default 和 import

ES Module 是原生实现的模块化方案,与 CommonJS 有以下几个区别

    1. CommonJS 支持动态导入,也就是 require(${path}/xx.js),后者目前不支持,但是已有提案
    1. CommonJS 是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大。而ES Module是异步导入,因为用于浏览器,需要下载文件,如果也采用同步导入会对渲染有很大影响
    1. CommonJS 在导出时都是值拷贝,就算导出的值变了,导入的值也不会改变,所以如果想更新值,必须重新导入一次。但是 ES Module 采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入值会跟随导出值变化
    1. ES Module 会编译成 require/exports 来执行的
// 引入模块 API
import XXX from './a.js'
import { XXX } from './a.js'
// 导出模块 API
export function a() {}
export default function() {}

★ var、let 及 const

常见面试题 1.var、let 及 const 区别;2.什么是提升;3.什么是暂时性死区

var、let 及 const 区别

(1)块级作用域:  块作用域由 { }包括,let和const具有块级作用域,var不存在块级作用域。块级作用域解决了ES5中的两个问题:

  • 内层变量可能覆盖外层变量
  • 用来计数的循环变量泄露为全局变量 (2)变量提升: var存在变量提升,在var声明之前使用不会报错,只是值是undefined而已,let和const不存在变量提升,即在变量只能在声明之后使用,否在会报错,即使上级作用域中定义了也不行,因为会读取当前作用域下的定义。
console.log(tmp); // undefined
var tmp = 123;
console.log(tmp); // 123
if (true) {
//   console.log(tmp); //ReferenceError: tmp is not defined
let tmp;
console.log(tmp);// undefined  上面报错就不会执行到这,报错注释掉,这里会是undefined
}

(3)给全局添加属性: 浏览器的全局对象是window,Node的全局对象是global。var声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但是let和const不会。

(4)重复声明: var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量。【会报错 Identifier 'a' has already been declared】

(5)暂时性死区: 在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用var声明的变量不存在暂时性死区。

(6)初始值设置: 在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。

(7)指针指向: let和const都是ES6新增的用于创建变量的语法。 let创建的变量是可以更改指针指向(可以重新赋值)。但const声明的变量是不允许改变指针的指向。

区别varlet【es6】const【es6】
是否有块级作用域×✔️✔️
是否存在变量提升✔️××
是否添加全局属性✔️ var即使再块级作用域里面定义的也是全局变量××
能否重复声明变量✔️××
是否存在暂时性死区×✔️✔️
是否必须设置初始值××✔️
能否改变指针指向✔️✔️× const是不允许重新指向新的内存地址
  • 函数提升优先于变量提升,函数提升会把整个函数挪到作用域顶部,变量提升只会把声明挪到作用域顶部

  • var 存在提升,我们能在声明之前使用。letconst 因为暂时性死区的原因,不能在声明前使用

  • var 在全局作用域下声明变量会导致变量挂载在 window 上,其他两者不会

  • let 和 const 作用基本一致,但是后者声明的变量不能再次赋值块作用域由 { }包括,let和const具有块级作用域,var不存在块级作用域。块级作用域解决了ES5中的两个问题:

  • 内层变量可能覆盖外层变量

  • 用来计数的循环变量泄露为全局变量

1.首先letconst是es6的;

2.letconst具有块级作用域,var不存在块级作用域;

3.var存在变量提升【函数提升优先于变量提升】,letconst不存在变量提升;

4.var声明的变量为全局变量,并且会添加为全局对象的属性,但是letconst不会。

5.var可以重复声明变量,后面的会覆盖之前的。constlet不允许重复声明变量。
【会报错   Identifier 'a' has already been declared】

6.letconst存在暂时性死区,var没有;

7.varlet 可以不用设置初始值 const必须设置初始值;

8.const 不能再次赋值  varlet 可以

什么是提升

console.log(a) // undefined
var a = 1

对于这种情况,我们可以把代码这样来看

var a
console.log(a) // undefined
a = 1

从上述代码中我们可以发现,虽然变量还没有被声明,但是我们却可以使用这个未被声明的变量,这种情况就叫做提升,并且提升的是声明。

不仅变量会提升函数也会被提升。

console.log(a) // ƒ a() {}
function a() {}
var a = 1

对于上述代码,打印结果会是 ƒ a() {},即使变量声明在函数之后,这也说明了函数会被提升,并且优先于变量提升。

什么是暂时性死区

let a = 1 
function b (){
    console.log(a)  // 1
    // let a = 2
}
b()




// 暂时性死区
let a = 1 
function b (){
    console.log(a)  // 报错 截图如下
    let a = 2
}
b()

上面代码中,存在全局变量`a`,但是块级作用域内`let`又声明了一个局部变量`a`,
导致后者a绑定这个块级作用域,所以在`let`声明变量前,对`a`赋值会报错。

image.png

ES6明确规定,如果区块中存在letconst命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

总之,在代码内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称TDZ)。