前端面试

ES6 新特性

2023-02-21  本文已影响0人  Sue1024

let const var

  1. var 存在变量提升,变量可以在声明前被使用,值为undefinedlet ``var不可以在声明前被使用,否则会报错,ES6 明确规定,如果区块中存在letconst`命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
function test() {
    console.log(a); //undefind
    var a = 1;
    console.log(b); //Uncaught ReferenceError: Cannot access 'b' before initialization
    let b = 2;
}

这就引发了一个叫暂时性死区的现象,所谓暂时性死区,就是只要在同一作用域内,包括函数和块级全局,letconst 就会绑定在这个区域,在这之前使用的话,都会报错,直到声明过后。举个例子:

var num = 2
function test() {
    console.log(num); //Uncaught ReferenceError: Cannot access 'num' before initialization
    const num = 1;
}
  1. var不存在块级作用域,letconst存在块级作用域。ES6前,Javascript只区分全局作用域(整个script标签内部或者一个独立的js文件)和函数作用域(局部作用域),ES5不存在块级作用域(凡是用{}包起来的都算),所以对于var来说:
function test() {
    for(var i=0; i<10; i++) {}
    console.log(i); //10
}

对于let来说:

function test() {
    for(let i=0; i<10; i++) {}
    console.log(i); // Uncaught ReferenceError: i is not defined
}
  1. 初始值
    var let 可以不设置初始值,const必须设置初始值
const a; // Uncaught SyntaxError: Missing initializer in const declaration
  1. 重复声明
    var 可以重复声明, let const不允许
  2. 数据修改
    varlet允许修改数据或者重新赋值,const定义的如果是基本数据类型,是不允许修改的,如果是引用数据类型,那么保存在栈中的堆地址是不可以修改的,真正的数据可以修改。

解构

  1. 数组解构
const [a, b, c, d] = [1,2,3,4]
console.log(a,b,c,d); // 1 2 3 4
  1. 对象解构
const {a, b, c, d} ={a: 1, b: 2, c: 3, d: 4}
console.log(a,b,c,d); // 1 2 3 4

模版字符串

var names = ["Tom", "Jane", "Tim"]
var name =  `${names} are coming.`

箭头函数

  1. 3分钟理解箭头函数的this
  2. 没有arguments
function test(){
  console.log(arguments)
}
test(1,2,3); // Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
const test2 = () =>{
  console.log(arguments)
}
test2(); // Uncaught ReferenceError: arguments is not definedat test2
  1. 不能通过 new 关键字调用, 根据new的原理来看,箭头函数不具备调用的条件:
test.prototype
// {constructor: ƒ}
test2.prototype
// undefined

这里简单描述一下new Test('abc')的调用过程:

形参默认值

有默认值的形参位置要放到最后

function add(a,b,c=2) {
    console.log(a + b + c);
}
add(1,2) //5

与解构赋值结合使用 结构赋值的先后不影响

function connect({name, age=18, sex}) {
    console.log(name);
    console.log(age);
    console.log(sex);
  }
  connect({
    name:'小宝',
    sex: 'man'
  })

Symbol

Symbol是ES6新引入的一种原始数据类型,表示独一无二的值,常用于命名不能冲突的场景,比如对象的key,或者定义常量替换无意义的字符串,创建时直接Symbol(),不能使用new
作为key:

const MY_KEY = Symbol();
let obj = {};
obj[MY_KEY] = 123;
console.log(obj[MY_KEY]); // 123
let obj2 = {
  [MY_KEY]: 123
};
console.log(obj2[MY_KEY]); // 123
let obj3 = {
  [MY_KEY]() {
    return 'bar';
  }
};
console.log(obj3[MY_KEY]()); // bar

作为常量

const levels = {
  DEBUG: Symbol('debug'),
  INFO: Symbol('info'),
  WARN: Symbol('warn'),
}

function log(type, message) {
  switch (type) {
    case levels.DEBUG:
      console.log(message)
      break
    case levels.INFO:
      console.log(message)
      break
    case levels.WARN:
      console.log(message)
      break
    default:
      console.log('default')
      break
  }
}

Symbol.for(key)

Symbol.for(key)通过key(一个字符串,作为 symbol 注册表中与某 symbol关联的键,同时也会作为该 symbol 的描述)来判断其唯一性,key必须是字符串或者可以被转换成字符串(因此Symbol类型不能作为key,不过很难想象它有toString方法却不能被动转换成字符串),不是字符串的,调用toString()转换为字符串,如果无法转换成字符串的,会报错。undefinednull 没有toString(),但是不会报错,当做字符串'undefined''null'处理,默认值即为undefined。返回由给定的 key 找到的 symbol,否则就是返回新创建的symbol
判断Symbol.for(key)的返回值是否相等其实就是在判断两个keytoString()返回结果是否相等.

Symbol.for([1,2,3]) === Symbol.for('1,2,3'); // true
Symbol.for() === Symbol.for(undefined); // true
Symbol.for() === Symbol.for('undefined'); // true
Symbol.for(null) === Symbol.for('null'); // true
Symbol.for({}) === Symbol.for({a: 123}); // true
Symbol.for([]) === Symbol.for(""); // true
Symbol.for(Infinity) === Symbol.for("Infinity"); // true
Symbol.for(NaN) === Symbol.for("NaN"); // true

Symbol.iterator

Symbol.iterator是一个内置值如果对象有Symbol.iterator, 即obj[Symbol.iterator] !== undefined那么这个对象就可以被for...of遍历

for (let i of [1,2,3] ) {
  console.log(i); // 1 2 3
}

for (let i of {num1: 1, num2: 2} ) {
  console.log(i); // Uncaught TypeError: {(intermediate value)(intermediate value)} is not iterable
}

因此如何让一个对象可以被for...of:

  1. 给对象添加一个keySymbol.iterator的属性方法
  2. 这个方法必须返回一个迭代器对象,它的结构必须如下:
{
    next: function() {
        return {
            value: any, //每次迭代的结果
            done: boolean //迭代结束标识
        }
    }
}

举个例子:

obj={names: ["Tom", "Jane", "Tim"];
obj[Symbol.iterator] = function () {
  let i = 0
  const _this = this
  return {
    next: () => {
      return { 
        value: _this.names[i++], 
        done: i === _this.names.length 
      }
    },
  }
}
for(let i of obj) {
    console.log(i); // Tom Jane Tim
}

获得属性名称

const MY_KEY = Symbol()
let obj2 = {
  [MY_KEY]: 123,
  enum: 2,
  nonEnum: 3,
}
console.log(Object.getOwnPropertyNames(obj2)) // ['enum', 'nonEnum']
console.log(Object.getOwnPropertySymbols(obj2)) // [Symbol()]
console.log(Reflect.ownKeys(obj2)) // ['enum', 'nonEnum', Symbol()]

Set

类似数组,但成员值都是唯一的,可以方便的去重,求并集、交集、差集。

Map

Promise

有三种状态pending fulfilled rejected

创建实例

new Promise(function(resolve, reject) {...})
创建Promise实例时,需要往构造方法中传入一个函数作为参数,这个函数可以通过调用传入的resolvereject方法来改变promise实例的状态,调用resolvepending转变为fulfilled,调用rejectpending转变为rejected

实例方法

  1. then 实例状态发生变化时,触发的回调函数,第一个参数是 resolved状态的回调函数,第二个参数是 rejected 的回调函数(一般使用 catch 方法来替代第二个参数)
  2. catch 用于指定发生错误的回调函数
  3. finally 用于指定 不管 Promise对象最后状态如何,都会执行的操作

静态方法

  1. Promise.all 将多个 Promise 实例包装成一个新的 Promise 实例
    const p = Promise.all([p1, p2, p3]);
    只有 p1,p2,p3 状态全为 fulfilledp 的状态才会变成fulfilled。此时,p1,p2,p3 的返回值组成一个数组,传递给 p 的回调函数。
    只要 p1,p2,p3 有一个状态为 rejected ,那么 p 的状态就变成 rejected。此时第一个被reject的实例的返回值,会传递给p的回调函数。

  2. Promise.race 将多个 Promise实例包装成一个新的 Promise 实例
    const p = Promise.race([p1, p2, p3]);
    三者谁先改变状态, p 也就会跟着改变状态。率先改变的会将返回值传递给 p 的回调函数。

await async

async 是加在函数前的修饰符,被async定义的函数会默认返回一个Promise对象的实例,因此被async标记的函数可以直接then。
await也是一个修饰符,只能使用于被标记为async的方法内,取到值时才会往下执行。

浅聊 promise setTimeout aysnc await

console.log('start')
setTimeout(() => {
    console.log('setTimeout complete')
})
setTimeout(() => {
    new Promise((resolve, reject) => {
        for(let i =0;i<5;i++) {
        }
        console.log('promise3 internal function complete')
        resolve()
    }).then(() => {
        console.log('promise3 complete')
    });new Promise((resolve, reject) => {
        for(let i =0;i<5;i++) {
        }
        console.log('promise4 internal function complete')
        resolve()
    }).then(() => {
        console.log('promise4 complete')
    });
})
new Promise((resolve, reject) => {
    for(let i =0;i<5;i++) {
    }
    console.log('promise internal function complete')
    resolve()
}).then(() => {
    console.log('promise complete')
});new Promise((resolve, reject) => {
    for(let i =0;i<5;i++) {
    }
    console.log('promise2 internal function complete')
    resolve()
}).then(() => {
    console.log('promise2 complete')
});
/**
console.log('end')
start
promise internal function complete
promise2 internal function complete
end
promise complete
promise2 complete
setTimeout complete
promise3 internal function complete
promise4 internal function complete
promise3 complete
promise4 complete
**/

以上代码的顺序大致可以描述为:

  1. script宏观任务
  2. 同步任务:console setTimeout Promise等初始化
  3. 所有微任务 promise1.then promise2.then
  4. 下一个宏任务 setTimeout1
  5. 同步任务: console
  6. 无微观任务,因此直接开始下一个宏观任务 setTimeout2
  7. 同步任务 Promise初始化
  8. 所有微任务 promise3.then promise4.then

深拷贝 浅拷贝

深拷贝后的所有数据均不受被拷贝的数据的影响,至于为什么有可能被影响不做赘述。
浅拷贝基本上是拷贝第一层的基本数据类型值,以及第一层的引用类型地址。

浅拷贝 Object.assign

Object.assign将一个或多个源对象的可枚举属性的值复制到目标对象,并返回目标对象。

const obj1 = {
    name: 'Sue',
    age: 18,
    getAge: () => this.age,
    key: Symbol('key'),
    mom: {
        name: 'Jane',
        age: 45,
        key: Symbol('key')
    }
}
const obj2 = {}
Object.assign(obj2, obj1)
// {name: 'Sue', age: 18, key: Symbol(key), mom: {…}, getAge: ƒ}
obj2.mom.age = 50
obj1.mom.age
// 50

以上例子可以证明使用Object.assign实现的是浅拷贝,且可以拷贝Symbol数据类型。

浅拷贝 数组

concat

let arr = [1, 2, 3];
let arr2 = arr.concat()

slice

let arr = [1, 2, 3];
let arr2 = arr.slice()

深拷贝 JSON.parse + JSON.stringify

let arr = [1, 3, { username: ' kobe' },function(){}];
let arr2 = JSON.parse(JSON.stringify(arr));
arr2[2].username = 'duncan'; 
console.log(arr[2].username): //kobe

这种方式不能拷贝函数

let arr = [1, 3, { username: ' kobe' },function(){}];
let arr2 = JSON.parse(JSON.stringify(arr));
arr2[2].username = 'duncan'; 
console.log(arr2[3]); // undefined

深拷贝 递归遍历

function checkType(value) {
  return Object.prototype.toString.call(value).slice(8, -1)
}

function deepClone(value) {
  const type = checkType(value);
  let result;
  if(type === 'Object') {
    result = {}
  } else if (type === 'Array') {
    result = []
  } else {
    return value
  }
  for(let i in value) {
    const _v = value[i];
    const _vt = checkType(_v);
    if (_vt === 'Array' || _vt === 'Object') {
      result[i] = deepClone(_v)
    } else {
      result[i] = _v
    }
  }
  return result
}
上一篇 下一篇

猜你喜欢

热点阅读