历史
version5.1 2011 年 6 月 version6 2015 年 6 月 版本最大的用处:polyfill 引入解析
ESNext -> ES的下一个版本
ES6及以后新增的常用API
let 和 const
面试题: 运行以下代码会输出什么?
for (var i = 0; i <= 3; i++) {
setTimeout(function () {
console.log(i);
}, 100);
}
// 4 4 4 4
原因:
- var 定义的变量是全局的,所以全局只有一个变量i (作用域)
- setTimeout,下一轮时间循环的时候执行,即循环为同步代码,赋值 i 之后进入到循环内部遇到setTimeout 将其callback 推入异步队列,当 i = 3 时,推入最后一个 callback,再执行 i++ . 此时由于是全局变量, i = 4,console.log开始跑 4 遍 console.log(4) (异步、同步)
面试:如何改变一下让代码生成0123
for (let i = 0; i <= 3; i++) {
setTimeout(function () {
console.log(i);
}, 100);
}
- let 引入了块级作用域的概念,创建setTimeout的时候,变量i只在作用域内生效。对于循环的每一次,引用的i都是不同的。
面试:如何不使用var,改变一下让代码生成0123
利用IIFE
for (var i = 0; i <= 3; i++) {
(function (i) {
setTimeout(function () {
console.log(i);
}, 100);
})(i)
}
- 变量提升问题
console.log(i); // undefined
var i = 1;
console.log(i); // 1
console.log(i) // ReferenceError
let i = 1;
console.log(i) // 1
- const 常量 通常可以写url
const i = 1;
i = 2; // TypeError
箭头函数
- 箭头函数的this是在定义的位置决定的,而不同函数里的this是在使用的时候决定的。
// 普通函数 this
const teacher = {
name: 'lubai',
getName: function () {
return this.name; // == teacher.name
}
}
console.log(teacher.getName()); // 'lubai'
// 箭头函数 this
const teacher = {
name: 'lubai',
getName: () => {
return this.name;
} // 在这里定义,此时的 this 为 window
}
console.log(teacher.getName()); // 'undefined' 这里相当于 window.name
- 简写
const arrowFn = (value) => Number(value);
const arrowFn = function (value) {
return Number(value);
}
以箭头左右来区分,左边的是参数,多个参数有括号,单个可省略括号,没有参数直接写括号; 箭头右面为返回值; 如果箭头右面写 {} 编译器默认认为是个函数体;如果要返回对象,需要写一个小括号将对象包起来
const arrowFn = () => {}
// 相当于
const arrowFn = function () {
return undefined;
}
// return 对象
const arrowFn = () => ({});
- 箭头函数不能被用作构造函数 构造函数:改变this指向,指到新实例。 箭头函数:this是在定义的时候决定的。
const arrowFn = () => ({
this.name; // 这里this指向window
})
const instance = new arrowFn();
// 这里不能改变this指向到instance
class
- constructor: 如果希望是可以被实例化的类,需要一个constructor
- 静态属性 static
- getter setter 实现了 class 属性的监听
class Test {
_name = '';
constructor(name) {
this.name = name;
}
static getFormatName() {
return `${this._name} - 包装`;
}
get name() {
return this._name;
}
set name(val) {
console.log('检测到了name的赋值操作');
this._name = val;
}
}
const instance = new Test('lubai');
console.log(instance);
// 检测到了name的赋值操作 --> 实例化的时候做了一个set name的赋值操作
// lubai --> constructor
// 静态属性调用
console.log(Test.getFormatName());
// Test - 包装
ES6 类的实例属性,相当于ES5 中的Test.prototype.name
模版字符串
// ES6 以前
const a = 'lubai';
console.log(a + 'cool!')
// 模版字符串
console.log(`${a} cool!`)
- 可以直接显示换行
const b = `第一行
第二行
第三行`
// ES5
const a = 'lubai\nlubai2\nlubai3';
面试题:写一个render函数,实现tempalte功能
const year = '2021';
const month = '10';
const day = '01';
const template = '${year}-${month}-${day}';
const context = { year, month, day } // { year: 2021, month: 10, day: 01 }
const str = render(template)(context) // 高阶函数:运行一个函数,返回另一个函数
// 相当于
/*
function render(template) {
return function(context) {
}
}
*/
// 输出
console.log(str) // 2021-10-01
// code here
function render(template) {
return function (context) {
return template.replace(/\$\{(.*?)\}/g, (match, key) => context[key]);
}
}
//.*? 包括数字等任何字符
// () 为获得的参数作为后面回调的 key 传入
解构
- 数组的解构
// ES5
let arr = [1,2,3];
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
// ES6
let [a, b, c] = [1,2,3]
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
let [a, b] = [{
name: 'lubai'
}, {
name: 'xxx'
}]
console.log(a); // { name: 'lubai'}
console.log(b); // { name: 'xxx'}
- 对象的解构
const {
f1, f2
} = {
f1: 'f1',
f2: 'f2'
}
console.log(f1);
console.log(f2);
-
解构的原理
针对可迭代对象Iterator,通过遍历按顺序获取对应的值进行赋值。
3.1 Iterator 是什么?
Iterator 是一种接口,interface,为各种不一样的数据提供统一的访问机制,任何数据解构只要有Iterator接口,就可以解构可以通过Symbol.iterator 判断是否有Interable 接口
let num = 1; let obj = {}; console.log(num[Symbol.iterator]); // undefined console.log(obj[Symbol.iterator]); // undefinedfor of, 相当于一个遍历器,遍历数据的时候,去寻找Iterator,如果找不到,就会报错
const obj = { name: 'lisa', age: 19 } for (key of obj) { console.log(key); } // TypeError: obj is not iterable3.2 Iterator 有什么用呢?
- 为各种不同的数据解构提供统一的接口。
- 数据解构按顺序处理
- for of 可以进行遍历
实现一个生成可迭代对象函数
可迭代对象中应有next函数,函数中每个以对象应该有两个属性:value,donefunction generateIterator (array) { let arrayIndex = 0; return { next: () => arrayIndex < array.length ? { value: array[arrayIndex++], done: false } : { value: undefined, done: true } } } const iterator = generateIterator([0,1,2]); console.log(iterator.next()); // { value: 0, done: false } console.log(iterator.next()); // { value: 1, done: false } console.log(iterator.next()); // { value: 2, done: false } console.log(iterator.next()); // { value: undefined, done: true }3.3 可迭代对象是什么? 是Iterator接口的实现。
可迭代对象存在两种协议:可迭代协议,迭代器协议
- 可迭代协议:对象必须实现iterator方法,即其对象或者原型链上必须有一个属性名为Symbol.iterator: () => 迭代器协议 (一个没有参数的函数,返回 迭代器协议)
- 迭代器协议:定义了标准的方式来产生一个有限或无限序列值。其必须要有一个 next 方法,next方法返回对象 done value 属性
面试题:写一个函数实现对象可以使用for of 遍历 分析:
- 对象上必须有一个Symbol.iterator
- 其值为一个没有参数的函数,返回 next 方法
- next 方法必须返回一个对象,包含 done 和 value 属性
const obj = {
count: 0,
[Symbol.iterator]: () => {
return {
next: () => {
obj.count++;
if (obj.count <= 10) {
return {
value: obj.count,
done: false
}
} else {
return {
value: undefined,
done: true
}
}
}
}
}
};
for (let item of obj) {
console.log(item);
}
遍历
- for in
遍历数组的时候,拿的是数组的下标; 遍历对象的时候,拿的是key,要拿value: obj[key]
let arr = [1,2,3]
let obj = {
name: 'lubai',
age: 18
}
for (let item in arr) {
console.log(item);
}
// 0 1 2
for(let key in obj) {
console.log(key);
}
// name age
for(let key in obj) {
console.log(obj[key]);
}
// lubai 18
缺点:
- 不仅会遍历当前对象,还会遍历原型链上的对象(某些第三方库更改了Object原型上的属性,导致也被遍历)
Object.prototype.protoName = 'ProtoName';
let obj = {
name: 'lubai',
age: 18
}
for(let key in obj) {
console.log(obj[key]);
}
// lubai 18 ProtoName
优化:需要判断当前obj有自己的似有属性 obj.hasOwnProperty()
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(obj[key]);
}
}
// lubai 18
- 不合适遍历数组: 因为遍历出的都是数组的索引 forEach 不会被 break 中断
- for of
- 可以被 break 中断
const arr = [{
age: 11
}, {
age: 6
}, {
age: 100
}]
for (let {age} of arr) {
if (age < 10) {
break;
}
console.log(age);
}
// 11
- 不适合遍历对象
Object
- Object.keys 拿到对象中的所有key组成一个数组
const obj = {
a: 1,
b: 2
};
console.log(Object.keys(obj));
// [ 'a', 'b' ]
面试:实现一个Object.keys? 注意要判断obj.hasOwnProperty
function getObjectKeys(obj) {
const result = [];
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result.push(key);
}
}
return result;
}
const obj = {
a: 1,
b: 2
};
console.log(getObjectKeys(obj));
// [ 'a', 'b' ]
- Object.values 拿到对象中所有value的值,组成一个数组
const obj = {
a: 1,
b: 2
};
console.log(Object.values(obj));
// [1, 2]
实现一个Object.values?
function getObjectValues(obj) {
const result = [];
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result.push(obj[key]);
}
}
return result;
}
console.log(getObjectValues(obj));
// [1,2]
- Object.entries 返回obj对象的键值对的数组
console.log(Object.entries(obj));
// [ [ 'a', 1 ], [ 'b', 2 ] ]
实现一个Object.entries?
function getObjectEntries(obj) {
const result = [];
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result.push([key, obj[key]]);
}
}
return result;
}
console.log(getObjectEntries(obj));
// [ [ 'a', 1 ], [ 'b', 2 ] ]
- Object.getOwnProperNames 拿到对象自身的属性名,返回一个数组
Object.prototype.protoName = 'ProtoName';
const obj = {
name: 'lubai',
age: 19
}
console.log(Object.getOwnPropertyNames(obj));
// [ 'name', 'age' ]
- Object.getOwnProperDescriptor 拿到自身对象的描述符 Descriptor 是对象属性的描述符,是一个对象。
这个对象包含若干个属性:
- configurable // 是否能够被配置 大开关:false的话,其他都无效
- writable // 是否可写 只有在'use strict' 模式下才生效
- enumerable // 可否枚举 for in /Object.keys 等
- Object.defineOwnProperty:
const obj = {};
let val = undefined;
Object.defineProperty(obj, 'a', {
set: function (value) {
console.log(`${value} - xxxx`);
val = value;
},
get: function () {
return val;
},
configurable: true,
})
obj.a = 111;
console.log(obj.a)
// 111 - xxxx 首先触发 setter
// 111 通过getter 拿到
面试问题:Vue3 与 Vue2 对于数据劫持有什么不同的实现? Proxy
const obj = new Proxy({}, {
get: function (target, propKey, receiver) {
console.log(`getting ${propKey}`);
return target[propKey];
},
set: function (target, propKey, value, receiver) {
console.log(`setting ${propKey}`);
return Reflect.set(target, propKey, value, receiver);
}
});
obj.something = 1;
console.log(obj.something);
// setting something
// getting something 1
Reflect 是什么?
- 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法
- 让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。
- Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。
- Object.assign 浅拷贝 {...a, ...b} 将所有的对象进行合并,后面的属性覆盖前面的属性
const newObj = Object.assign({}, {name: 'xxx', age: 19}, {name: 'lubai'});
console.log(newObj);
// { name: 'lubai', age: 19 }
实现一个浅拷贝
function shallowClone (source) {
let target = {};
for (let i in source) {
if (source.hasOwnProperty(i)) {
target[i] = source[i];
}
}
return target;
}
const a = {
a: 'xxx',
b: 2,
c: {
d: 'x'
}
}
const b = shallowClone(a);
b.c.d = 'y';
console.log(a);
// { a: 'xxx', b: 2, c: { d: 'y' } }
- Object.create()
Object.create()方法创建一个新的对象,并以方法的第一个参数作为新对象的__proto__属性的值(根据已有的对象作为原型,创建新的对象。) Object.create()方法还有第二个可选参数,是一个对象,对象的每个属性都会作为新对象的自身属性,对象的属性值以descriptor(Object.getOwnPropertyDescriptor(obj, 'key'))的形式出现,且enumerable默认为false
const person = {
isHuman: false,
printIntroduction: function () {
console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
}
};
const me = Object.create(person);
me.name = "lubai";
me.isHuman = true;
me.printIntroduction();
console.log(person);
const myObject = Object.create(null)
传入第二个参数是怎么操作的呢?
function Person(name, sex) {
this.name = name;
this.sex = sex;
}
const b = Object.create(Person.prototype, {
name: {
value: 'coco',
writable: true,
configurable: true,
enumerable: true,
},
sex: {
enumerable: true,
get: function () {
return 'hello sex'
},
set: function (val) {
console.log('set value:' + val)
}
}
})
console.log(b.name)
console.log(b.sex)
那么Object.create(null)的意义是什么呢? 平时创建一个对象Object.create({}) 或者 直接声明一个{} 不就够了?
Object.create(null)创建一个对象,但这个对象的原型链为null,即Fn.prototype = null
const b = Object.create(null) // 返回纯{}对象,无prototype
b // {}
b.__proto__ // undefined
b.toString() // throw error
所以当你要创建一个非常干净的对象, 没有任何原型链上的属性, 那么就使用Object.create(null). for in 遍历的时候也不需要考虑原型链属性了.
- Object.is 方法判断两个值是否为同一个值
const a = {
name: 1
};
const b = a;
console.log(Object.is(a, b))
console.log(Object.is({}, {}))
Promise
Promise.allSettled 返回所有的promise的状态和结果
function PromiseAllSettled(promiseArray) {
return new Promise(function (resolve, reject) {
//判断参数类型
if (!Array.isArray(promiseArray)) {
return reject(new TypeError('arguments muse be an array'))
}
let counter = 0;
const promiseNum = promiseArray.length;
const resolvedArray = [];
for (let i = 0; i < promiseNum; i++) {
Promise.resolve(promiseArray[i])
.then((value) => {
resolvedArray[i] = {
status: 'fulfilled',
value
};
})
.catch(reason => {
resolvedArray[i] = {
status: 'rejected',
reason
};
})
.finally(() => {
counter++;
if (counter == promiseNum) {
resolve(resolvedArray)
}
})
}
})
}
数组
- Array.flat(arr, 指定深度) 将多围数组打平为规定纬度数组
const arr1 = [1,2,[3,4]]
console.log(arr1.flat())
// [ 1, 2, 3, 4 ]
const arr1 = [1,2,[3,4,[5]]]
console.log(arr1.flat(1));
// [ 1, 2, 3, 4, [ 5 ] ]
打平所有层级:Array.flat(Infinity) 实现Array.flat()
const arr1 = [1,2,[3,4,[5]]]
function flatDeep(arr, d = 1) {
if (d > 0) {
return arr.reduce((res, val) => {
if (Array.isArray(val)) {
res = res.concat(flatDeep(val, d - 1));
} else {
res = res.concat(val);
}
return res;
}, []);
} else {
return arr.slice(); // 不改变原数组,浅拷贝
}
}
console.log(flatDeep(arr1, Infinity))
// [ 1, 2, 3, 4, 5 ]
Array.reduce(遍历函数, 存结果数组)
- Array.includes 判断元素是存在
const arr = [1,2,3,4,5]
console.log(arr.includes(6)); // false;
- Array.from(可迭代对象,map)
Array.map() 遍历整个数组,依次对数组元素执行回调函数并用这些返回值创建一个新的数组
const arr1 = [1,2,3,4,5]
console.log(Array.from(arr1, x => x + 1))
// [ 2, 3, 4, 5, 6 ]
面试题:如何把一个类数组(arguments)转成真数组??
- [...arguments]
- Array.from(arguments)
- Array.prototype.slice.call(arguments) // 相当于arguments.slice()
babel 编译工具的使用
astexplorer.net 查看AST
- 解析 解析代码 输出AST
- 词法分析
- 语法分析
-
转换 接收AST对其进行遍历,可以对节点进行添加、更新、移除等操作
-
生成 把转换过的AST生成为字符串形式的代码,并且创建source map (源码)
一个插件就是一个函数 plugin.js
const template = require('babel-template');
const temp = template("var b = 1")
module.exports = function ({
types: t
}) {
// 插件内容
return {
visitor: {
// 接收两个参数path, state
VariableDeclaration(path, state) {
// 找到AST节点
const node = path.node;
// 判断节点类型 是否是变量节点, 申明方式是const
if (t.isVariableDeclaration(node, {
kind: "const"
})) {
// 将const 声明编译为let
node.kind = "let";
// var b = 1 的AST节点
const insertNode = temp();
// 插入一行代码var b = 1
path.insertBefore(insertNode);
}
}
}
}
}
使用插件 babel.js
const myPlugin = require('./plugin')
const babel = require('@babel/core');
const content = 'const name = lubai';
// 通过你编写的插件输出的代码
const {
code
} = babel.transform(content, {
plugins: [
myPlugin
]
});
console.log(code);
面试题:webpack
- webpack的运行原理?
- wd 是怎么运行的?webpack dev server? 这些内容存在哪里?发开环境如何读取?
- 热重载怎么实现的?
- split chunk 是怎么实现的?