let const var
- var 存在变量提升,变量可以在声明前被使用,值为
undefined
;let
``var不可以在声明前被使用,否则会报错,ES6 明确规定,如果区块中存在
let和
const`命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
function test() {
console.log(a); //undefind
var a = 1;
console.log(b); //Uncaught ReferenceError: Cannot access 'b' before initialization
let b = 2;
}
这就引发了一个叫暂时性死区的现象,所谓暂时性死区,就是只要在同一作用域内,包括函数和块级全局,let
和const
就会绑定在这个区域,在这之前使用的话,都会报错,直到声明过后。举个例子:
var num = 2
function test() {
console.log(num); //Uncaught ReferenceError: Cannot access 'num' before initialization
const num = 1;
}
- var不存在块级作用域,
let
和const
存在块级作用域。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
}
- 初始值
var
let
可以不设置初始值,const
必须设置初始值
const a; // Uncaught SyntaxError: Missing initializer in const declaration
- 重复声明
var
可以重复声明,let
const
不允许 - 数据修改
var
和let
允许修改数据或者重新赋值,const
定义的如果是基本数据类型,是不允许修改的,如果是引用数据类型,那么保存在栈中的堆地址是不可以修改的,真正的数据可以修改。
解构
- 数组解构
const [a, b, c, d] = [1,2,3,4]
console.log(a,b,c,d); // 1 2 3 4
- 对象解构
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.`
箭头函数
- 3分钟理解箭头函数的this
- 没有
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
- 不能通过
new
关键字调用, 根据new的原理来看,箭头函数不具备调用的条件:
test.prototype
// {constructor: ƒ}
test2.prototype
// undefined
这里简单描述一下new Test('abc')的调用过程:
- 创建一个空对象
obj
- 把
obj
的__proto__
指向Test
的原型对象prototype
,此时便建立了obj
对象的原型链:obj->Animal.prototype->Object.prototype->null
- 在
obj
对象的执行环境调用Test
函数并传递参数'abc'
。 相当于var result = obj.Test('abc')
. - 考察第3步返回的返回值,如果无返回值或者返回一个非对象值,则将
obj
返回作为新对象;否则会将返回值作为新对象返回。
形参默认值
有默认值的形参位置要放到最后
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()
转换为字符串,如果无法转换成字符串的,会报错。undefined
和null
没有toString()
,但是不会报错,当做字符串'undefined'
和'null'
处理,默认值即为undefined
。返回由给定的 key
找到的 symbol
,否则就是返回新创建的symbol
。
判断Symbol.for(key)
的返回值是否相等其实就是在判断两个key
的toString()
返回结果是否相等.
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:
- 给对象添加一个
key
为Symbol.iterator
的属性方法 - 这个方法必须返回一个迭代器对象,它的结构必须如下:
{
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
实例时,需要往构造方法中传入一个函数作为参数,这个函数可以通过调用传入的resolve
和reject
方法来改变promise
实例的状态,调用resolve
由pending
转变为fulfilled
,调用reject
由pending
转变为rejected
实例方法
-
then
实例状态发生变化时,触发的回调函数,第一个参数是resolved
状态的回调函数,第二个参数是rejected
的回调函数(一般使用 catch 方法来替代第二个参数) -
catch
用于指定发生错误的回调函数 -
finally
用于指定 不管Promise
对象最后状态如何,都会执行的操作
静态方法
Promise.all
将多个Promise
实例包装成一个新的Promise
实例
const p = Promise.all([p1, p2, p3]);
只有p1
,p2
,p3
状态全为fulfilled
,p
的状态才会变成fulfilled
。此时,p1
,p2
,p3
的返回值组成一个数组,传递给p
的回调函数。
只要p1
,p2
,p3
有一个状态为rejected
,那么p
的状态就变成rejected
。此时第一个被reject
的实例的返回值,会传递给p
的回调函数。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
- 首先,这些不是同步任务。
- JS是单线程语言(注意区分线程和进程,而且仅JS是单线程,浏览器渲染等占用的是其他线程),在这个单线程上,会有同步任务和异步任务,异步任务又包括宏观任务和微观任务。
- 宏观任务包括(宿主发起):
script
(整体代码)setTimeout
setInterval
、I/O
UI交互事件
postMessage
MessageChannel
setImmediate
- 微观任务包括(JS引擎发起):
promise.then
MutationObserver
process.nextTick
await/async
(实际上是promise + generator 语法糖) - 执行顺序 所有的微任务都存在于某个宏任务中,所以一定是由宏任务开始执行。首先我们分析有多少个宏任务,在每个宏任务中,分析有多少个微任务,根据调用次序,确定宏任务中的微任务执行次序,根据宏任务的触发规则和调用次序,确定宏任务的执行次序,最后确定整个顺序。
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
**/
以上代码的顺序大致可以描述为:
-
script
宏观任务 - 同步任务:
console
setTimeout
Promise
等初始化 - 所有微任务
promise1.then
promise2.then
- 下一个宏任务
setTimeout1
- 同步任务:
console
- 无微观任务,因此直接开始下一个宏观任务
setTimeout2
- 同步任务
Promise
初始化 - 所有微任务
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
}