先导
个人在学习过程中,涉及和遇见的一些基础知识,对其进行了简单归纳总结,浅尝辄止,略显杂而不精,做个人参考用
注:内容基本都是摘抄自博客、网络或MDN等
ES6篇
1、var、let、const三者区别:
- 变量提升
- 暂时性死区
- 块级作用域
- 重复声明
- 修改声明的变量
- 使用
var:
声明的是全局变量,也是顶层变量
在函数中:使用var
声明变量的时候,该变量是局部的,而如果不使用var
,则该变量是全局的
注意:顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象
let:
let也会有提升,但是只是创建层面的提升,没有初始化,这样在使用时才会报错,形成暂时性死区
- let 的「创建」过程被提升了,但是初始化没有提升。
- var 的「创建」和「初始化」都被提升了。
- function 的「创建」「初始化」和「赋值」都被提升了。
const:
首先const
声明的变量不可改变,其次一旦声明必须初始化,const没有赋值
const
实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动
2、Generator
执行 Generator
函数会返回一个遍历器对象,可以依次遍历 Generator
函数内部的每一个状态
形式上,Generator
函数是一个普通函数,但是有两个特征:
function
关键字与函数名之间有一个星号- 函数体内部使用
yield
表达式,定义不同的内部状态
function* helloWorldGenerator() {
yield 'hello';
yield 'world'; // yield关键字可以暂停generator函数返回的遍历器对象的状态
return 'ending';
}
Generator
函数会返回一个遍历器对象,即具有Symbol.iterator
属性,并且返回给自己
运行逻辑:
-
遇到
yield
表达式,就暂停执行后面的操作,并将紧跟在yield
后面的那个表达式的值,作为返回的对象的value
属性值。返回对象:
{value: x, done: true/false}
-
下一次调用
next
方法时,再继续往下执行,直到遇到下一个yield
表达式 -
如果没有再遇到新的
yield
表达式,就一直运行到函数结束,直到return
语句为止,并将return
语句后面的表达式的值,作为返回的对象的value
属性值。 -
如果该函数没有
return
语句,则返回的对象的value
属性值为undefined
-
通过调用
next
方法可以带一个参数,该参数就会被当作上一个yield
表达式的返回值
可以直接用
for ... of
循环迭代generator
对象
优点
- 因为
generator
可以在执行过程中多次返回,所以它看上去就像一个可以记住执行状态的函数,利用这一点,写一个generator
就可以实现需要用面向对象才能实现的功能。 generator
还有另一个巨大的好处,就是把异步回调代码变成“同步”代码。
3、Proxy/Reflect
1)Proxy
定义: 用于定义基本操作的自定义行为
本质: 修改的是程序默认形为,就形同于在编程语言层面上做修改,属于元编程(meta programming)
元编程(Metaprogramming, 又译超编程,是指某类计算机程序的编写,这类计算机程序编写或者操纵其它程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作
理解: 创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
使用:
var proxy = new Proxy ( target , handler )
-
target
表示所要拦截的目标对象(任何类型的对象,包括原生数组,函数,甚至另一个代理)) -
handler
通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理p
的行为
hander解析
常用拦截属性有:
get(target,propKey,receiver)
:拦截对象属性的读取set(target,propKey,value,receiver)
:拦截对象属性的设置has(target,propKey)
:拦截propKey in proxy的操作,返回一个布尔值deleteProperty(target,propKey)
:拦截delete proxy[propKey]的操作,返回一个布尔值ownKeys(target)
:拦截Object.keys(proxy)、for...in等循环,返回一个数组getOwnPropertyDescriptor(target, propKey)
:拦截Object.getOwnPropertyDescriptor(proxy, propKey)
,返回属性的描述对象defineProperty(target, propKey, propDesc)
:拦截Object.defineProperty(proxy, propKey, propDesc)
,返回一个布尔值preventExtensions(target)
:拦截Object.preventExtensions(proxy),返回一个布尔值getPrototypeOf(target)
:拦截Object.getPrototypeOf(proxy),返回一个对象isExtensible(target)
:拦截Object.isExtensible(proxy),返回一个布尔值setPrototypeOf(target, proto)
:拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值apply(target, object, args)
:拦截 Proxy 实例作为函数调用的操作construct(target, args)
:拦截 Proxy 实例作为构造函数调用的操作
2)Reflect
使用Reflect
调用对象的默认行为(类似于其他语言的反射)
特点
- 只要
Proxy
对象具有的代理方法,Reflect对象全部具有,以静态方法的形式存在 - 修改某些Object方法的返回结果,让其变得更合理(定义不存在属性行为的时候不报错而是返回false)
- 让
Object
操作都变成函数行为
4、Decorator装饰器
Decorator,即装饰器,从名字上很容易让我们联想到装饰者模式
简单来讲,装饰者模式就是一种在不改变原类和使用继承的情况下,动态地扩展对象功能的设计理论。
ES6
中Decorator
功能亦如此,其本质也不是什么高大上的结构,就是一个普通的函数,用于扩展类属性和类方法
用法
Docorator
修饰对象为下面两种:
- 类的装饰
- 类属性的装饰
和java
基本一模一样
优点
- 代码可读性变强了,装饰器命名相当于一个注释
- 在不改变原有代码情况下,对原来功能进行扩展
5、Promise和async/await
1)Promise
(1)基础
-
promise
创建后会立即执行,resolve
是用来变更promise
的状态为fulfilled
,只有当状态改变后,then
中的函数才会被推入微任务。 -
在其中调用
resolve()
,仅相当于一个函数调用,改变状态,后面的语句会继续执行 -
promise
后面的then
是一起注册的,所以要注意执行顺序 -
如果某一个
then
里面出现:return
语句,那下一个then
要等这个return
执行之后的结果
关于执行顺序:
事件机制是 “先注册先执行”,下一个then
的注册需要上一个then
的同步代码执行完成,这里所说的 then
的注册,是指微任务队列的注册,并不是 .then
的方法的执行,没有注册的会等待
-
链式调用的注册是前后依赖的:比如
new promise().then.then
-
变量定义的方式,注册都是同步的:比如
p = new Promise(), p.then()
他们都是同步执行的。
(2)Promise API
Promise.all
接收一个promise
数组参数,返回时的数组元素顺序和参数一致
Promise.all
可以将多个Promise
实例包装成一个新的Promise
实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject
失败状态的值。
示例:
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://api.github.com/users/jeresig'
];
// 将每个 url 映射(map)到 fetch 的 promise 中
let requests = urls.map(url => fetch(url));
// Promise.all 等待所有任务都 resolved
Promise.all(requests)
.then(responses => responses.forEach(
response => alert(`${response.url}: ${response.status}`)
));
Promise.allSettled
Promise.allSettled
等待所有的 promise
都被 settle
,无论结果如何。结果数组具有:
{status:"fulfilled", value:result}
对于成功的响应,{status:"rejected", reason:error}
对于 error。
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://no-such-url'
];
Promise.allSettled(urls.map(url => fetch(url)))
.then(results => { // (*)
results.forEach((result, num) => {
if (result.status == "fulfilled") {
alert(`${urls[num]}: ${result.value.status}`);
}
if (result.status == "rejected") {
alert(`${urls[num]}: ${result.reason}`);
}
});
});
/*
结果results
[
{status: 'fulfilled', value: ...response...},
{status: 'fulfilled', value: ...response...},
{status: 'rejected', reason: ...error object...}
]
*/
Promise.race
同样接收一个promise数组, 但是返回最快响应的那个,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
Promise.any
Promise.any
只等待第一个 fulfilled
的 promise
,并将这个 fulfilled
的 promise
返回。如果给出的 promise
都 rejected
,那么则返回 rejected
的 promise
和 AggregateError
错误类型的 error 实例—— 一个特殊的 error 对象,在其 errors
属性中存储着所有 promise error。
2)async/ await
async/await
在底层转换成了 promise
和 then
回调函数。可以理解为是 promise
+ generator
的语法糖。
async
函数调用不会造成阻塞,它会将其后的函数(函数表达式或 Lambda)的返回值封装成一个 Promise
对象异步执行。
await
暂停当前async
的执行,然后把剩下的 async
函数中的操作放到 then
回调函数中。等待这个 Promise
完成,将其 resolve
的结果返回出来。
注:
await
会阻塞后面的任务,指的是下一行代码,await
同行代码是会立即执行的
(1)async:
用 async
标识的函数,会返回promise
对象,
- 如果
async
关键字函数返回的不是promise
,会自动用Promise.resolve()
包装 - 如果
async
关键字函数显式地返回promise
,那就以你返回的promise
为准
例如:
async function fn1(){
console.log(123)
}
console.log(fn1())
// 打印如下
Promise {
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: undefined
}
可以看见两点:
- 状态是
fulfilled
,说明自动resolve
了 - 无返回值,结果是
undefined
(2)await:
await
只在异步函数里面才起作用。它可以放在任何异步的,基于 promise
的函数之前
语法:[return_value] = await expression
;
表达式(expression
):一个 Promise
对象或者任何要等待的值。
返回值(return_value
):返回 Promise
对象的处理结果。如果等待的不是 Promise
对象,则返回该值本身。
注:await这个语句是从右向左执行的,即先执行
expression
,然后遇见await
,跳出返回,先执行async外的同步代码
参考阮一峰的理解:async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。
参考题:
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
// 此时返回的Promise会被放入到回调队列中等待,await会让出线程(js是单线程),
// 接下来就会跳出 async1函数 继续往下执行。
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve(); // 同理,放到回调队列
}).then(function() {
console.log('promise2');
});
console.log('script end');
/**
输出:
> script start
> async1 start
> async2
> promise1
> script end
> async1 end
> promise2
> undefined
> setTimeout
*/
6、Class
ES6 的类,完全可以看作构造函数的另一种写法,所以类的数据类型就是函数,而类本身就指向构造函数,
类的底层实现,还是依赖于原型链,事实上,类的所有方法都定义在类的prototype
属性上面。
1)Class基础
-
类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。
-
静态方法
static
:Class本身的方法,静态方法中的this
指类,而不是示例,通过类直接调用 -
静态属性:Class本身的属性
-
私有方法和私有属性实现:
私有方法
- 命名上进行区别(私有的前面加下划线
_prop
) - 将私有方法移出模块
- 利用
Symbol
值的唯一性,将私有方法的名字命名为一个Symbol
值
私有属性
- 在属性名之前,使用
#
表示(提案)
- 命名上进行区别(私有的前面加下划线
-
在“类”的内部可以使用
get
和set
关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
class MyClass {
constructor() {
// ...
}
get prop() {
return 'getter';
}
set prop(value) {
console.log('setter: '+value);
}
}
let inst = new MyClass();
inst.prop = 123;
// setter: 123
inst.prop
// 'getter'
2)Class继承
ES6 的继承机制实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this
-
extends
:class ColorPoint extends Point{}
-
super
方法:表示父类的构造函数,用来新建父类的this
对象。 子类必须在constructor方法中调用super方法,因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。 -
Object.getPrototypeOf
方法可以用来从子类上获取父类。 -
super
对象:super
作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类 -
类的
prototype
属性和__proto__
属性Class 作为构造函数的语法糖,同时有
prototype
属性和__proto__
属性,因此同时存在两条继承链。
(1)子类的
__proto__
属性,表示构造函数的继承,总是指向父类。
(2)子类prototype
属性的__proto__
属性,表示方法的继承,总是指向父类的prototype
属性。 (3)子类实例的__proto__
属性的__proto_
_属性,指向父类实例的__proto__
属性。也就是说,子类的原型的原型,是父类的原型。
实例:
class A {
}
class B {
}
Object.setPrototypeOf = function (obj, proto) {
obj.__proto__ = proto;
return obj;
}
// B 的实例继承 A 的实例
Object.setPrototypeOf(B.prototype, A.prototype);
// B 继承 A 的静态属性
Object.setPrototypeOf(B, A);
const b = new B();
作为一个对象,子类(B)的原型(__proto__
属性)是父类(A);
作为一个构造函数,子类(B)的原型对象(prototype
属性)是父类的原型对象(prototype
属性)的实例。
个人理解是:子类的原型是父类,相当于父类构造函数的prototype
对象
7、WeakMap、WeakSet
1)WeakMap
JavaScript 引擎在值“可达”和可能被使用时会将其保持在内存中。
所以如果我们使用对象作为常规 Map
的键,那么当 Map
存在时,该对象也将存在。它会占用内存,并且应该不会被(垃圾回收机制)回收。
WeakMap
在这方面有着根本上的不同。它不会阻止垃圾回收机制对作为键的对象(key object
)的回收。
WeakMap
和Map
的第一个不同点就是,WeakMap 的键必须是对象,不能是原始值- WeakMap 只有以下的方法:
weakMap.get(key)
weakMap.set(key, value)
weakMap.delete(key)
weakMap.has(key)
因为如果一个对象丢失了其它所有引用,那么它就会被垃圾回收机制自动回收。但是在从技术的角度并不能准确知道 何时会被回收。
这些都是由 JavaScript 引擎决定的。因此,从技术上讲,WeakMap
的当前元素的数量是未知的。JavaScript 引擎可能清理了其中的垃圾,可能没清理,也可能清理了一部分。因此,暂不支持访问 WeakMap
的所有键/值的方法。
使用场景:
-
WeakMap
的主要应用场景是 额外数据的存储假如我们正在处理一个“属于”另一个代码的一个对象,也可能是第三方库,并想存储一些与之相关的数据,那么这些数据就应该与这个对象共存亡 —— 这时候
WeakMap
正是我们所需要的利器。 -
缓存
我们可以存储(“缓存”)函数的结果,以便将来对同一个对象的调用可以重用这个结果。
如果使用
Map
,缺点是,当我们不再需要这个对象的时候需要清理 cache。如果我们用
WeakMap
替代Map
,便不会存在这个问题。当对象被垃圾回收时,对应缓存的结果也会被自动从内存中清除。
2)WeakSet
WeakSet
的表现类似:
- 与
Set
类似,但是我们只能向WeakSet
添加对象(而不能是原始值)。 - 对象只有在其它某个(些)地方能被访问的时候,才能留在
set
中。 - 跟
Set
一样,WeakSet
支持add
,has
和delete
方法,但不支持size
和keys
(),并且不可迭代。
它也可以作为额外的存储空间。但并非针对任意数据,而是针对“是/否”的事实。WeakSet 的元素可能代表着有关该对象的某些信息。
例如,我们可以将用户添加到 WeakSet
中,以追踪访问过我们网站的用户
WeakMap
和 WeakSet
最明显的局限性就是不能迭代,并且无法获取所有当前内容。那样可能会造成不便,但是并不会阻止WeakMap/WeakSet
完成其主要工作 ——成为在其它地方管理/存储“额外”的对象数据。
3)总结
WeakMap
是类似于 Map
的集合,它仅允许对象作为键,并且一旦通过其他方式无法访问它们,便会将它们与其关联值一同删除。
WeakSet
是类似于 Set
的集合,它仅存储对象,并且一旦通过其他方式无法访问它们,便会将其删除。
它们的主要优点是它们对对象是弱引用,所以被它们引用的对象很容易地被垃圾收集器移除。
这是以不支持 clear
、size
、keys
、values
等作为代价换来的……
WeakMap
和 WeakSet
被用作“主要”对象存储之外的“辅助”数据结构。一旦将对象从主存储器中删除,如果该对象仅被用作 WeakMap
或 WeakSet
的键,那么它将被自动清除。
8、如河判断是否为可迭代对象?
要成为可迭代对象, 一个对象必须实现 @@iterator
方法。这意味着对象(或者它原型链上的某个对象)必须有一个键为 @@iterator
的属性,可通过常量 Symbol.iterator
访问该属性:
[Symbol.iterator]
:一个无参数的函数,其返回值为一个符合迭代器协议的对象。
typeof obj[Symbol.iterator] === 'function';
9、箭头函数
普通函数箭头函数区别
- 箭头函数中的
this
指向在定义时继承自外层第一个普通函数的this。且不会改变 - 箭头函数没有原型,原型是
undefined
,所以本身没有this
call
、apply
、bind
改变不了箭头函数指向- 箭头函数不能作为构造函数使用
- 箭头函数不能用作Generator函数,不能使用yield关键字
- 箭头函数不绑定arguments,取而代之用rest参数…
10、柯里化
柯里化是一种函数的转换,它是指将一个函数从可调用的 f(a, b, c) 转换为可调用的 f(a)(b)(c)。
柯里化不会调用函数。它只是对函数进行转换
高级柯里化实现:
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) { // (1)
return func.apply(this, args);
} else {
return function(...args2) { // (2)
return curried.apply(this, args.concat(args2));
}
}
};
}
当我们运行它时,这里有两个 if 执行分支:
- 如果传入的
args
长度与原始函数所定义的(func.length
)相同或者更长,那么只需要使用func.apply
将调用传递给它即可。 - 否则,获取一个偏函数:我们目前还没调用
func
。取而代之的是,返回另一个包装器pass
,它将重新应用curried
,将之前传入的参数与新的参数一起传入。
然后,如果我们再次调用它,我们将得到一个新的偏函数(如果没有足够的参数),或者最终的结果。