Promise
一个 Promise
有以下几种状态:
- pending: 初始状态,既不是成功,也不是失败状态。
- fulfilled: 意味着操作成功完成。
- rejected: 意味着操作失败。
pending 状态的 Promise 对象可能会变为fulfilled 状态并传递一个值给相应的状态处理方法,也可能变为失败状态(rejected)并传递失败信息。当其中任一种情况出现时,Promise 对象的 then
方法绑定的处理方法(handlers )就会被调用(then方法包含两个参数:onfulfilled 和 onrejected,它们都是 Function 类型。当Promise状态为fulfilled时,调用 then 的 onfulfilled 方法,当Promise状态为rejected时,调用 then 的 onrejected 方法, 所以在异步操作的完成和绑定处理方法之间不存在竞争)。
因为
和 Promise.prototype.then
方法返回promise 对象, 所以它们可以被链式调用。Promise.prototype.catch
Promise接收一个函数,函数带有两个参数resolve,reject(这两个参数名可以自定义)
.then第一个参数接收resolve传递过来的值,第二个参数接收reject传递过来的值
1.resolve的东西,一定会进入then的第一个回调,肯定不会进入catch
2.reject后的东西,一定会进入then中的第二个回调,如果then中没有写第二个回调,则进入catch
var p1=function p1(a){ return new Promise((resolve,reject) => { if(a==1){ resolve("a") } else{ reject("b") } }) } p1(2).then(data =>{ console.log('data::',data); },err=> { console.log('err::',err) }).catch( res => { console.log('catch data::', res) })
let、const和var
let 、const 和 var 用于 for..of
如果使用 let 和 const ,每次迭代将会创建一个新的存储空间,这可以保证作用域在迭代的内部。
https://es6.ruanyifeng.com/#docs/let
之前的js世界里,我们定义变量都是使用的var,别说还真挺好用的,虽有会有一些问题,但是对于熟悉js特性的小伙伴都能很好的解决,一般记住:变量提升会解决绝大多数问题。
就能解决很多问题,而且真实项目中,我们会会避免出现变量出现重名的情况所以有时候大家面试题中看到的场景在实际工作中很少发生,只要不刻意臆想、制造一些难以判断的场景,其实并不会出现多少BUG,不能因为想考察人家对语言特性的了解,就做一些容易容易忘掉的陷阱题。
无论如何,var 声明的变量受到了一定诟病,事实上在强类型语言看来也确实是设计BUG,但是完全废弃var的使用显然不是js该做的事情,这种情况下出现了let关键词。
let与var一致用以声明变量,并且一切用var的地方都可以使用let替换,新的标准也建议大家不要再使用var了,let具有更好的作用域规则,也许这个规则是边界更加清晰了:
{ let a = 10; var b = 1; } a // ReferenceError: a is not defined. b // 1
这里是一个经典的闭包问题:
var a = []; for (var i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 10
因为i在全局范围有效,共享同一个作用域,所以i就只有10了,为了解决这个问题,我们之前会引入闭包,产生新的作用域空间(好像学名是变量对象,我给忘了),但是那里的i跟这里的i已经不是一个东西了,但如果将var改成let,上面的答案是符合预期的。可以简单理解为每一次“{}”,let定义的变量都会产生新的作用域空间,这里产生了循环,所以每一次都不一样,这里与闭包有点类似是开辟了不同的空间。
for (let i = 0; i < 3; i++) { let i = 'abc'; console.log(i); } // abc // abc // abc
这里因为内部重新声明了i,事实上产生了3个作用域,这里一共有4个作用域指向,let最大的作用就是js中块级作用域的存在,并且内部的变量不会被外部所访问,所以之前为了防止变量侮辱的立即执行函数,似乎变得不是那么必要了。
proxy
Proxy.revocable(target,handler);
注释:可撤销Proxy
句法
Proxy.revocable(target,handler);
参量
target
- 要包装的目标对象
Proxy
。它可以是任何类型的对象,包括本机数组,函数甚至其他代理。 handler
- 一个对象,其属性是定义对代理执行操作时的行为的函数。
返回值
Proxy
返回一个新创建的可撤消对象。
描述
可撤销Proxy
对象是具有以下两个属性的对象{proxy: proxy, revoke: revoke}
。
proxy
- 使用
new Proxy(target, handler)
call 创建的代理对象。 revoke
- 没有参数的函数会使()无效(关闭)
proxy
。
如果revoke()
调用该函数,则代理将不可用:处理程序的任何陷阱都将引发TypeError
。代理被吊销后,它将保持吊销状态,并且可以进行垃圾回收。revoke()
再次呼叫无效。
var revocable = Proxy.revocable({}, { get: function(target, name) { return "[[" + name + "]]"; } }); var proxy = revocable.proxy; console.log(proxy.foo); // "[[foo]]" revocable.revoke(); console.log(proxy.foo); // TypeError is thrown proxy.foo = 1 // TypeError again delete proxy.foo; // still TypeError typeof proxy // "object", typeof doesn't trigger any trap
for...of 是 ES6 新引入的循环,用于替代 for..in 和 forEach() ,并且支持新的迭代协议。它可用于迭代常规的数据类型,如 Array 、 String 、 Map 和 Set 等等。
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
proxy有两个参数
Proxy句法
var p = new Proxy(target, handler);
target
要包装的目标对象Proxy
。它可以是任何类型的对象,包括本机数组,函数甚至其他代理。
handler
一个对象,其属性是定义对代理执行操作时的行为的函数。
get方法
get句法
var p = new Proxy(target, { get: function(target, property, receiver) { } });
参量
以下参数将传递给该get
方法。this
绑定到处理程序。
(1).target:必需,获取属性的目标对象。
(2).propertyKey:必需,属性名称。
(3).receiver:可选,如果指定属性部署了getter访问器,那么此访问器的this就会指向receiver。
返回值
该get
方法可以返回任何值。
描述
该handler.get
方法是获取属性值的陷阱。
案例分析:
const obj = { key: 1, b:2, c:3 } const hand={ get:function(a,b,c){ if(b=="b"){ return "a" } else if(b=="c"){ return "c" } else{ return "e" } } } const proxy = new Proxy(obj, hand) console.log(proxy.key,proxy.c)//e c
上面代码中Proxy有两个参数,obj代表Proxy句法中的参数target, hand代表Proxy句法中的参数handler
#target
get的三个参数a就是对应get句法中的target代表obj, b就是对应get句法中的property代表obj的属性, c就是对应get句法中的receiver代表proxy本身
yield
- 如果你看到某个函数中有yield,说明这个函数已经是个生成器了
- yield可以用来加强控制,懒汉式加载
- 调用函数指针和调用生成器是两码事
- 需要next()函数配合使用,每次调用返回两个值:分别是value和done,代表迭代结果和是否完成
- 函数next()是个迭代器对象,传参可以缺省,默认调用函数。
- next() 传参是对yield整体的传参,否则yield类似于return
-
yield并不能直接生产值,而是产生一个等待输出的函数
除IE外,其他所有浏览器均可兼容(包括win10 的Edge)
某个函数包含了yield,意味着这个函数已经是一个Generator
如果yield在其他表达式中,需要用()单独括起来yield表达式本身没有返回值,或者说总是返回undefined(由next返回)next()可无限调用,但既定循环完成之后总是返回undeinded
Generator

function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } var hw = helloWorldGenerator();
Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是上一章介绍的遍历器对象(Iterator Object)。
下一步,必须调用遍历器对象的next
方法,使得指针移向下一个状态。也就是说,每次调用next
方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield
表达式(或return
语句)为止。换言之,Generator 函数是分段执行的,yield
表达式是暂停执行的标记,而next
方法可以恢复执行。

hw.next() // { value: 'hello', done: false } hw.next() // { value: 'world', done: false } hw.next() // { value: 'ending', done: true } hw.next() // { value: undefined, done: true }
上面代码一共调用了四次next
方法。
第一次调用,Generator 函数开始执行,直到遇到第一个yield
表达式为止。next
方法返回一个对象,它的value
属性就是当前yield
表达式的值hello
,done
属性的值false
,表示遍历还没有结束。
第二次调用,Generator 函数从上次yield
表达式停下的地方,一直执行到下一个yield
表达式。next
方法返回的对象的value
属性就是当前yield
表达式的值world
,done
属性的值false
,表示遍历还没有结束。
第三次调用,Generator 函数从上次yield
表达式停下的地方,一直执行到return
语句(如果没有return
语句,就执行到函数结束)。next
方法返回的对象的value
属性,就是紧跟在return
语句后面的表达式的值(如果没有return
语句,则value
属性的值为undefined
),done
属性的值true
,表示遍历已经结束。
第四次调用,此时 Generator 函数已经运行完毕,next
方法返回对象的value
属性为undefined
,done
属性为true
。以后再调用next
方法,返回的都是这个值。
总结一下,调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的next
方法,就会返回一个有着value
和done
两个属性的对象。value
属性表示当前的内部状态的值,是yield
表达式后面那个表达式的值;done
属性是一个布尔值,表示是否遍历结束。
ES6 没有规定,function
关键字与函数名之间的星号,写在哪个位置。这导致下面的写法都能通过。

function * foo(x, y) { ··· } function *foo(x, y) { ··· } function* foo(x, y) { ··· } function*foo(x, y) { ··· }
由于 Generator 函数仍然是普通函数,所以一般的写法是上面的第三种,即星号紧跟在function
关键字后面。本书也采用这种写法。
for...of
循环Iterator 的遍历过程是这样的。
(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
(2)第一次调用指针对象的next
方法,可以将指针指向数据结构的第一个成员。
(3)第二次调用指针对象的next
方法,指针就指向数据结构的第二个成员。
(4)不断调用指针对象的next
方法,直到它指向数据结构的结束位置。
每一次调用next
方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value
和done
两个属性的对象。其中,value
属性是当前成员的值,done
属性是一个布尔值,表示遍历是否结束。
Object.freeze(obj)
参量
obj
- 冻结的对象。
返回值
传递给函数的对象。
不能将任何东西添加到冻结对象的属性集中或从中删除。这样做的任何尝试都将失败,无论是通过静默方式还是通过引发TypeError异常(在严格模式下,这是最常见的(但不是排他的))都会失败。
对于冻结对象的数据属性,无法更改值,可写和可配置属性设置为false。访问器属性(获取器和设置器)的工作原理相同(并且仍然给人一种幻想,即您正在更改值)。请注意,除非将对象值冻结,否则仍可以对其进行修改。作为对象,可以冻结数组。这样做之后,就不能更改其元素,并且不能将任何元素添加到数组或从数组中删除。
freeze()返回传递给函数的同一对象。它不会创建冻结副本。
Symbol
https://www.jianshu.com/p/f40a77bbd74e
注意:使用 Symbol 只能声明假的私有属性,就如上所说通过 Object.getOwnPropertySymbols(login),可以获得 login 所有的 Symbol 属性。要声明真正的私有属性,可以使用 WeakMap 类,它是 Map 类的弱化版本,即没有 entries、keys 和 values 等迭代器方法,只能用对象作为健,必须用健才可以取出值,除非你知道健,否则没有办法取出值。在函数内声明这个变量,用 WeakMap 类来存储这个变量,以 this 为健,让这个变量只能在函数类使用,如此就成为了真正的私有属性,但扩展类无法继承私有属性。
Array.prototype.map()和Array.prototype.forEach()
Array.prototype.map()不修改原函数,返回修改的数组,Array.prototype.forEach()修改原数组,返回undefault
如果你已 Array.prototype.map()经有使用JavaScript的经验,你可能已经知道这两个看似相同的方法:Array.prototype.map()和Array.prototype.forEach()。
那么,它们到底有什么区别呢?
定义
我们首先来看一看MDN上对Map和ForEach的定义:
forEach(): 针对每一个元素执行提供的函数(executes a provided function once for each array element)。
map(): 创建一个新的数组,其中每一个元素由调用数组中的每一个元素执行提供的函数得来(creates a new array with the results of calling a provided function on every element in the calling array)。
到底有什么区别呢?forEach()方法不会返回执行结果,而是undefined。也就是说,forEach()会修改原来的数组。而map()方法会得到一个新的数组并返回。
示例
下方提供了一个数组,如果我们想将其中的每一个元素翻倍,我们可以使用map和forEach来达到目的。
1
|
let arr = [1, 2, 3, 4, 5]; |
ForEach
注意,forEach是不会返回有意义的值的。
我们在回调函数中直接修改arr的值。
1
2
3
|
arr.forEach((num, index) => { return arr[index] = num * 2; }); |
执行结果如下:
1
|
// arr = [2, 4, 6, 8, 10] |
Map
1
2
3
|
let doubled = arr.map(num => { return num * 2; }); |
执行结果如下:
1
|
// doubled = [2, 4, 6, 8, 10] |
执行速度对比
jsPref是一个非常好的网站用来比较不同的JavaScript函数的执行速度。
这里是forEach()和map()的测试结果:
可以看到,在我到电脑上forEach()的执行速度比map()慢了70%。每个人的浏览器的执行结果会不一样。你可以使用下面的链接来测试一下: Map vs. forEach - jsPref。
JavaScript太灵(gui)活(yi)了,出了BUG你也不知道,不妨接入Fundebug线上实时监控。
函数式角度的理解
如果你习惯使用函数是编程,那么肯定喜欢使用map()。因为forEach()会改变原始的数组的值,而map()会返回一个全新的数组,原本的数组不受到影响。
哪个更好呢?
取决于你想要做什么。
forEach适合于你并不打算改变数据的时候,而只是想用数据做一些事情 – 比如存入数据库或则打印出来。
1
2
3
4
5
6
7
8
|
let arr = [ 'a' , 'b' , 'c' , 'd' ]; arr.forEach((letter) => { console.log(letter); }); // a // b // c // d |
map()适用于你要改变数据值的时候。不仅仅在于它更快,而且返回一个新的数组。这样的优点在于你可以使用复合(composition)(map(), filter(), reduce()等组合使用)来玩出更多的花样。
1
2
3
|
let arr = [1, 2, 3, 4, 5]; let arr2 = arr.map(num => num * 2).filter(num => num > 5); // arr2 = [6, 8, 10] |
我们首先使用map将每一个元素乘以2,然后紧接着筛选出那些大于5的元素。最终结果赋值给arr2。
核心要点
能用forEach()做到的,map()同样可以。反过来也是如此。
map()会分配内存空间存储新数组并返回,forEach()不会返回数据。
forEach()允许callback更改原始数组的元素。map()返回新的数组。