导航


HTML

CSS

JavaScript

浏览器 & 网络

版本管理

框架

构建工具

TypeScript

性能优化

软实力

算法

UI、组件库

Node

冷门技能

let、const

⭐ let & const 与 var 的区别?

⭐ 为什么已经有 var 了, 还要再推出 let 和 const

因为 var 存在两个特性或者说问题:

  1. 变量提升
  2. 重复声明(同一作用域下)
  3. if、for代码块中声明外界却可以访问

而 let、const 解决了这些问题:

  1. 声明之前无法使用
  2. 同一作用域下无法重复声明
  3. 使用的是块级作用域,if、for 这样的代码块中不对外界有干扰

⭐ 全局作用域中,用 const 和 let 声明的变量不在 window 上,那到底在哪里?如何去获取?

在 ES5 中,顶层对象的属性和全局变量是等价的,var 命令和 function 命令声明的全局变量,自然也是顶层对象。

var a = 12;
function f() {};
console.log(window.a); // 12
console.log(window.f); // f() {}

但 ES6 规定,var 命令和 function 命令声明的全局变量,依旧是顶层对象的属性,但 let 命令、const 命令、class 命令声明的全局变量,不属于顶层对象的属性。

let aa = 1;
const bb = 2;
console.log(window.aa); // undefined
console.log(window.bb); // undefined

在哪里?怎么获取?通过在设置断点,看看浏览器是怎么处理的:

Untitled

通过上图也可以看到,在全局作用域中,用 let 和const 声明的全局变量并没有在全局对象中,只是一个块级作用域(Script)中

怎么获取?在定义变量的块级作用域中就能获取啊,既然不属于顶层对象,那就不加 window(global)呗。

let aa = 1;
const bb = 2;
console.log(aa); // 1
console.log(bb); // 2

箭头函数

⭐ 箭头函数与普通函数的区别?

  1. 箭头函数不能作为构造函数,不能 new
  2. 箭头函数没有自己的 this
  3. 箭头函数没有 arguments 对象
  4. 箭头函数没有原型对象
const a = () => 1;
console.log(a.prototype); // undefined

剩余函数

Symbol

Symbol 的应用场景?

异步

setTimeout 的机制

等到当前脚本的同步任务和“任务队列”中已有的事件,全部处理完以后,才会执行 setTimeout 指定的任务

参考:JavaScript 运行机制详解:再谈Event Loop

处理异步的方法有哪些?

⭐ 什么是 Promise?解决了什么问题?

⭐️ Promise 失败重试,可指定重试次数

来源:https://www.mulingyuer.com/archives/758/

//模拟异步请求
function axiosFn() {
    return new Promise((resolve, reject) => {
        const flge = Math.random(); //随机值
        setTimeout(() => {
            //大于0.7就是成功
            if (flge > 0.7) {
                return resolve(flge);
            } else {
                return reject(flge);
            }
        }, 1000)
    })
}

/**
 * @description: promise 失败重试方法
 * @param {*} fn 异步函数
 * @param {*} times 次数
 * @Date: 2022-02-20 22:47:54
 * @Author: mulingyuer
 */
Promise.retry = function(fn, times) {
    return new Promise(async (resolve, reject) => {
        while (times--) {
            try {
                const res = await fn();
                //请求成功,结束while循环
                return resolve(res);
            } catch (error) {
                //请求失败
                if (times > 0) {
                    console.log(`请求失败正在重试,还剩${times}次,错误信息为:${error}`);
                } else {
                    console.log(`请求失败,重试${times}次后,还是失败,错误信息为:${error}`);
                    return reject(error);
                }
            }
        }
    })
}

//测试
Promise.retry(axiosFn, 3).then(res => {
    console.log(`获得的数据为:${res}`);
}).catch(error => {
    console.log(`失败了,错误信息为:${error}`);
});

⭐ 什么是 async/await?解决了什么问题?

是 generator + Promise 的语法糖,主要的作用是用同步方式执行异步操作,await 只能在 async 函数中使用,async 函数执行会返回一个 Promise,值由函数的 return 值所决定

JS 异步解决方案的发展历程以及优缺点

  1. 回调函数(callback)
    1. 优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队 等着,会拖延整个程序的执行)
    2. 缺点:回调地狱,不能用 try catch 捕获错误,不能 return
  2. Promise
    1. 优点:解决了回调地狱的问题
    2. 缺点:无法取消 Promise ,错误需要通过回调函数来捕获
  3. Generator
    1. 特点:可以控制函数的执行,可以配合 co 函数库使用
    2. 缺点:不直观,稍微难以理解
  4. Async / await
    1. 优点:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题
    2. 缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使 用 await 会导致性能上的降低

⭐ 实现一个 sleep 函数

比如 sleep(1000) 意味着等待1000毫秒,可从 Promise、Generator、Async/Await 等角度实现。

// Promise
const sleep = delay => new Promise(resolve => setTimeout(resolve, delay))
sleep(1000).then(() => console.log('Done'));

// Generator
function* sleepGenerator(delay) {
  yield new Promise(resolve => setTimeout(resolve, delay))
}

sleepGenerator(1000).next().value.then(() => console.log('Done'));

// async / await
const sleep = delay => new Promise(resolve => setTimeout(resolve, delay))
async function output() {
  await sleep(1000)
  console.log('Done')
}
output()

// ES5
function sleep(cb, delay) {
  if (typeof cb === 'function')
    setTimeout(cb, delay)
}
function output() {
  console.log('Done')
}
sleep(output, 1000)

⭐  a、b、c 三个请求,希望 c 在 a、b 获取数据后再发请求,要是你你会怎么做?

let fs = require('fs')
let arr = []
function fn(data) {
  arr.push(data)
  if (arr.length === 2) {
  	console.log(arr)
  }
}
fs.readFile('./name.text', 'utf8', (err, data) => {
	fn(data)
})
fs.readFile('./age.text', 'utf8', (err, data) => {
	fn(data)
})
let fs = require('fs')
let arr = []
let i = 0
function fn(data, index) {
  arr[index] = data
  if (++i === 2) {
  	console.log(arr)
  }
}
fs.readFile('./name.text', 'utf8', (err, data) => {
	fn(data, 1)
})
fs.readFile('./age.text', 'utf8', (err, data) => {
	fn(data, 0)
})
let fs = require('fs')
function after(times, cb) {
  let arr = []
	return function(data) {
    arr.push(data)
    if (--times === 0) {
      cb(arr)
    }
  }
}
let newFn = after(3, function(arr) {
	console.log(arr)
})
fs.readFile('./name.text', 'utf8', (err, data) => {
	newFn(data)
})
fs.readFile('./age.text', 'utf8', (err, data) => {
	newFn(data)
})
fs.readFile('./weight.text', 'utf8', (err, data) => {
	newFn(data)
})

⭐ 实现函数 fn

fn([
  () => console.log('start'),
  () => sleep(1000),
  () => console.log('1'),
  () => sleep(2000),
  () => console.log('2'),
  () => sleep(3000),
  () => console.log('end'),
]);

实现:

function sleep(delay) {
	return new Promise(resolve => setTimeout(resolve, delay));
}
async function fn(arr) {
	for (let i = 0; i < arr.length; i++) {
  	await arr[i]();
  }

  // 或者

  // for (const item of arr) {
  //   await item();
  // }
}

fn([
  () => console.log('start'),
  () => sleep(1000),
  () => console.log('1'),
  () => sleep(2000),
  () => console.log('2'),
  () => sleep(3000),
  () => console.log('end'),
]);

如何用 forEach 来实现

function sleep(delay) {
	return new Promise(resolve => setTimeout(resolve, delay));
}
async function fn(arr) {
	arr.forEach(item => {
  	await item();
  });
}

fn([
  () => console.log('start'),
  () => sleep(1000),
  () => console.log('1'),
  () => sleep(2000),
  () => console.log('2'),
  () => sleep(3000),
  () => console.log('end'),
]);

实现:

Array.prototype.forEach = async function(callback, thisArg) {
  let _arr = this,
      isArray = Array.isArray(_arr),
      _thisArg = thisArg ? Object(thisArg) : window;

  if (!isArray) {
  	throw new TypeError('forEach调用者得是个数组');
  }

  for (let i = 0; i < _arr.length; i++) {
    await callback.call(_thisArg, _arr[i], i, _arr);
  }
}

function sleep(delay) {
	return new Promise(resolve => setTimeout(resolve, delay));
}
async function fn(arr) {
	arr.forEach(async item => {
  	await item();
  });
}

fn([
  () => console.log('start'),
  () => sleep(1000),
  () => console.log('1'),
  () => sleep(2000),
  () => console.log('2'),
  () => sleep(3000),
  () => console.log('end'),
]);

在 Promise 链式调用中,怎样才能保证上一个 promise 出现报错不会影响到后续 .then 的正常执行

为了不影响后续.then的执行,需要在每一个then中指定失败的回调

let asyncFunc = () => new Promise(resolve => {
  resolve("123");									// 123, 二楼
  // throw new Error("出错了");		// Error: 出错了, 二楼
});

asyncFunc().then(res => {
  console.log(res);
  return Promise.resolve("二楼");
}, err => {												<====== 指定失败的回调
  console.log(err);
    return Promise.resolve("二楼");
}).then(res => {
  console.log(res);
});

⭐ 实现一个函数,对一个 url 进行请求,失败就再次请求,超过最大次数就走失败回调,任何一次成功都走成功回调

/**
 @params url: 请求接口地址;
 @params body: 设置的请求体;
 @params succ: 请求成功后的回调
 @params error: 请求失败后的回调
 @params maxCount: 设置请求的数量
*/
function request(url, body, succ, error, maxCount = 5) {
 return fetch(url, body)
     .then(res => succ(res))
     .catch(err => {
         if (maxCount <= 0) return error('请求超时');
         return request(url, body, succ, error, --maxCount);
     });
}

// 调用请求函数
request('<https://js.some.com/pc/reqCount>', { method: 'GET', headers: {} },
 (res) => {
     console.log(res.data);
 },
 (err) => {
     console.log(err);
 })

宏任务与微任务有哪些?

宏任务

| --- | --- | --- |

微任务

| --- | --- | --- |

⭐ 打印预测

var p1 = Promise.resolve( 1 );
var p2 = Promise.resolve( p1 );
var p3 = new Promise(function(resolve, reject){
  resolve(1);
});
var p4 = new Promise(function(resolve, reject){
  resolve(p1);
});

// 宏任务:
// 微任务:

console.log(p1 === p2);
console.log(p1 === p3);
console.log(p1 === p4);
console.log(p3 === p4);

p4.then(function(value){
  console.log('p4=' + value);
});

p2.then(function(value){
  console.log('p2=' + value);
})

p1.then(function(value){
  console.log('p1=' + value);
})
// 答案在下

// true
// false
// false
// false
// p2=1
// p1=1
// p4=1

Promise.resolve(...)可以接收一个值或者是一个Promise对象作为参数。
当参数是普通值时,它返回一个resolved状态的Promise对象,对象的值就是这个参数;
当参数是一个Promise对象时,它直接返回这个Promise参数。因此,p1 === p2。
但通过new的方式创建的Promise对象都是一个新的对象,因此后面的三个比较结果都是false。
另外,为什么p4的then最先调用,但在控制台上是最后输出结果的呢?
因为p4的resolve中接收的参数是一个Promise对象p1,resolve会对p1”拆箱“,获取p1的状态和值,
但这个过程是异步的