本文共 13398 字,大约阅读时间需要 44 分钟。
symbol 是一种基本数据类型,表示独一无二的值。它是JavaScript 语言的第七种数据类型,是一种类似于字符串的数据类型。Symbol()
函数会返回symbol类型的值,该类型具有静态属性和静态方法。但作为构造函数来说它并不完整,因为它不支持语法:"new Symbol()
"。
每个从Symbol()
返回的symbol值都是唯一的。一个symbol值能作为对象属性的标识符;这是该数据类型仅有的目的。
语法:
Symbol([description])
description
可选的,字符串类型。对symbol的描述,可用于调试(在控制台可以看见,方便区分)但不是访问symbol本身。
描述:
直接使用Symbol()
创建新的symbol类型,并用一个可选的字符串作为其描述。
var sym1 = Symbol();var sym2 = Symbol('foo');var sym3 = Symbol('foo');Symbol("foo") === Symbol("foo"); // false
上面的代码创建了三个新的symbol类型。 注意,Symbol("foo")
不会强制将字符串 “foo” 转换成symbol类型。它每次都会创建一个新的 symbol类型:
全局共享的Symbol
上面使用Symbol()
函数的语法,不会在你的整个代码库中创建一个可用的全局的symbol类型。 要创建跨文件可用的symbol,甚至跨域(每个都有它自己的全局作用域),使用 方法和 方法从全局的symbol注册表设置和取得symbol。
在对象中查找Symbol属性
方法让你在查找一个给定对象的符号属性时返回一个symbol类型的数组。注意,每个初始化的对象都是没有自己的symbol属性的,因此这个数组可能为空,除非你已经在对象上设置了symbol属性。
Symbol.length
:长度属性,值为0。
Symbol.prototype
: symbol
构造函数的原型。
除了定义自己使用的Symbol 值以外,ES6 还提供了11 个内置的Symbol 值,指向语言内部使用的方法。可以称这些方法为魔术方法,因为它们会在特定的场
景下自动执行。(了解,需要了去查就可以)Symbol.for(key)
:根据给定的键 key
,从运行时的 symbol 注册表中找到对应的 symbol,如果找到了,则返回它,否则,新建一个与该键关联的 symbol,并放入全局 symbol 注册表中。
Symbol.for("foo"); // 创建一个 symbol 并放入 symbol 注册表中,键为 "foo"Symbol.for("foo"); // 从 symbol 注册表中读取键为"foo"的 symbolSymbol.for("bar") === Symbol.for("bar"); // true,证明了上面说的Symbol("bar") === Symbol("bar"); // false,Symbol() 函数每次都会返回新的一个 symbolvar sym = Symbol.for("mario");sym.toString();// "Symbol(mario)",mario 既是该 symbol 在 symbol 注册表中的键名,又是该 symbol 自身的描述字符串
Symbol.keyFor(sym)
:用来获取全局symbol 注册表中与某个 symbol 关联的键
// 创建一个全局 Symbolvar globalSym = Symbol.for("foo");Symbol.keyFor(globalSym); // "foo"var localSym = Symbol();Symbol.keyFor(localSym); // undefined,// 以下Symbol不是保存在全局Symbol注册表中Symbol.keyFor(Symbol.iterator) // undefined
对 symbol 使用 typeof 运算符
const symbol1 = Symbol();const symbol2 = Symbol(42);const symbol3 = Symbol('foo');console.log(typeof symbol1);// expected output: "symbol"console.log(symbol2 === 42);// expected output: falseconsole.log(symbol3.toString());// expected output: "Symbol(foo)"console.log(Symbol('foo') === Symbol('foo'));// expected output: false
Symbols 在 迭代中不可枚举。另外, 不会返回 symbol 对象的属性,但是你能使用 得到它们。
var obj = { };var a = Symbol("a");var b = Symbol.for("b");obj[a] = "localSymbol";obj[b] = "globalSymbol";var objectSymbols = Object.getOwnPropertySymbols(obj);console.log(objectSymbols.length); // 2console.log(objectSymbols) // [Symbol(a), Symbol(b)]console.log(objectSymbols[0]) // Symbol(a)
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理且更强大。
是一个对象,它代表了一个异步操作的最终完成或者失败。本质上 Promise 是一个函数返回的对象,我们可以在它上面绑定回调函数,这样我们就不需要在一开始把回调函数作为参数传入这个函数了。
假设现在有一个名为 createAudioFileAsync()
的函数,它接收一些配置和两个回调函数,然后异步地生成音频文件。一个回调函数在文件成功创建时被调用,另一个则在出现异常时被调用。
以下为使用 createAudioFileAsync()
的示例:
// 成功的回调函数function successCallback(result) { console.log("音频文件创建成功: " + result);}// 失败的回调函数function failureCallback(error) { console.log("音频文件创建失败: " + error);}createAudioFileAsync(audioSettings, successCallback, failureCallback)
更现代的函数会返回一个 Promise 对象,使得你可以将你的回调函数绑定在该 Promise 上。
如果函数 createAudioFileAsync()
被重写为返回 Promise 的形式,那么我们可以像下面这样简单地调用它:
const promise = createAudioFileAsync(audioSettings);promise.then(successCallback, failureCallback);
或者简写为:
createAudioFileAsync(audioSettings).then(successCallback, failureCallback);
对象的状态不受外界影响 (3种状态)
一旦状态改变就不会再变 (两种状态改变:成功或失败)
只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
创建Promise实例
var promise = new Promise(function(resolve, reject){ // ... some code if (/* 异步操作成功 */) { resolve(value); } else { reject(error); }})
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数,由JavaScript引擎提供,不用自己部署。resolve作用是将Promise对象状态由“未完成”变为“成功”,也就是Pending -> Fulfilled
,在异步操作成功时调用,并将异步操作的结果作为参数传递出去;而reject函数则是将Promise对象状态由“未完成”变为“失败”,也就是Pending -> Rejected
,在异步操作失败时调用,并将异步操作的结果作为参数传递出去。
promise.then()
对于已经实例化过的 promise 对象可以调用 promise.then() 方法,传递 resolve 和 reject 方法作为回调。
var myFirstPromise = new Promise(function(resolve, reject){ //当异步代码执行成功时,我们才会调用resolve(...), 当异步代码失败时就会调用reject(...) //在本例中,我们使用setTimeout(...)来模拟异步代码,实际编码时可能是XHR请求或是HTML5的一些API方法. setTimeout(function(){ resolve("成功!"); //代码正常执行! }, 250);}); myFirstPromise.then(function(successMessage){ //successMessage的值是上面调用resolve(...)方法传入的值. //successMessage参数不一定非要是字符串类型,这里只是举个例子 document.write("Yay! " + successMessage); //Yay! 成功!});
执行顺序:接下来我们探究一下它的执行顺序,看以下代码:
let promise = new Promise(function(resolve, reject){ console.log("AAA"); resolve()});promise.then(() => console.log("BBB"));console.log("CCC")// AAA// CCC// BBB
执行后,我们发现输出顺序总是 AAA -> CCC -> BBB
。表明,在Promise新建后会立即执行,所以首先输出 AAA
。然后,then方法指定的回调函数将在当前脚本所有同步任务执行完后才会执行,所以BBB 最后输出
。
与定时器混用:首先看一个实例:
let promise = new Promise(function(resolve, reject){ console.log("1"); resolve();});setTimeout(()=>console.log("2"), 0);promise.then(() => console.log("3"));console.log("4");// 1// 4// 3// 2
可以看到,结果输出顺序总是:1 -> 4 -> 3 -> 2
。1与4的顺序不必再说,而2与3先输出Promise的then,而后输出定时器任务。原因则是Promise属于JavaScript引擎内部任务,而setTimeout则是浏览器API,而引擎内部任务优先级高于浏览器API任务,所以有此结果。
作者:流眸Tel
参考链接:https://www.jianshu.com/p/b16e7c9e1f9f连续执行两个或者多个异步操作是一个常见的需求,在上一个操作执行成功之后,开始下一个的操作,并带着上一步操作所返回的结果。我们可以通过创造一个 Promise 链来实现这种需求。
then()
函数会返回一个和原来不同的新的 Promise:
const promise = doSomething();const promise2 = promise.then(successCallback, failureCallback);//或者简写为const promise2 = doSomething().then(successCallback, failureCallback);
promise2
不仅表示 doSomething()
函数的完成,也代表了你传入的 successCallback
或者 failureCallback
的完成,这两个函数也可以返回一个 Promise 对象,从而形成另一个异步操作,这样的话,在 promise2
上新增的回调函数会排在这个 Promise 对象的后面。
基本上,每一个 Promise 都代表了链中另一个异步过程的完成。
在过去,要想做多重的异步操作,会导致经典的回调地狱:
doSomething(function(result) { doSomethingElse(result, function(newResult) { doThirdThing(newResult, function(finalResult) { console.log('Got the final result: ' + finalResult); }, failureCallback); }, failureCallback);}, failureCallback);
现在,我们可以把回调绑定到返回的 Promise 上,形成一个 Promise 链:
doSomething().then(function(result) { return doSomethingElse(result);}).then(function(newResult) { return doThirdThing(newResult);}).then(function(finalResult) { console.log('Got the final result: ' + finalResult);}).catch(failureCallback);
then 里的参数是可选的,catch(failureCallback)
是 then(null, failureCallback)
的缩略形式。如下所示,我们也可以用来表示:
doSomething().then(result => doSomethingElse(result)).then(newResult => doThirdThing(newResult)).then(finalResult => { console.log(`Got the final result: ${ finalResult}`);}).catch(failureCallback);
注意:一定要有返回值,否则,callback 将无法获取上一个 Promise 的结果。(如果使用箭头函数,() => x
比 () => { return x; }
更简洁一些,但后一种保留 return
的写法才支持使用多个语句。)。
有可能会在一个回调失败之后继续使用链式操作,即,使用一个 catch
,这对于在链式操作中抛出一个失败之后,再次进行新的操作会很有用。请阅读下面的例子:
new Promise((resolve, reject) => { console.log('初始化'); resolve();}).then(() => { throw new Error('有哪里不对了'); console.log('执行「这个」”');}).catch(() => { console.log('执行「那个」');}).then(() => { console.log('执行「这个」,无论前面发生了什么');});//初始化//执行“那个”//执行“这个”,无论前面发生了什么
注意:因为抛出了错误 有哪里不对了,所以前一个 执行「这个」 没有被输出。
Promise 对象的错误具有"冒泡"性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个 catch 语句捕获。
Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
var p = Promise.all([p1,p2,p3]);
上面代码中,Promise.all 方法接受一个数组作为参数,p1、p2、p3 都是 Promise 对象的实例。(Promise.all 方法的参数不一定是数组,但是必须具有 iterator 接口,且返回的每个成员都是 Promise 实例。)
p 的状态由 p1、p2、p3 决定,分成两种情况。
Promise.race 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
var p = Promise.race([p1,p2,p3]);
上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的返回值。
如果Promise.all方法和Promise.race方法的参数,不是Promise实例,就会先调用Promise.resolve方法,将参数转为Promise实例,再进一步处理。
有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数(回调地狱)。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。
Promise 也有一些缺点。首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
遍历器(Iterator)就是一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator 接口,就可以完成遍历操作。
原生具备 iterator 接口的数据(可用for of 遍历)
for...of
语句在(包括 ,,,,, 、NodeList对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句
语法
for (variable of iterable) { //statements}
迭代Array
let iterable = [10, 20, 30];for (let value of iterable) { value += 1; console.log(value);}// 11// 21// 31
迭代String
let iterable = "boo";for (let value of iterable) { console.log(value);}// "b"// "o"// "o"
迭代Set
let iterable = new Set([1, 1, 2, 2, 3, 3]);for (let value of iterable) { console.log(value);}// 1// 2// 3
迭代Map
let iterable = new Map([["a", 1], ["b", 2], ["c", 3]]);for (let entry of iterable) { console.log(entry);}// ["a", 1]// ["b", 2]// ["c", 3]for (let [key, value] of iterable) { console.log(value);}// 1// 2// 3
迭代Arguments对象
(function() { for (let argument of arguments) { console.log(argument); }})(1, 2, 3);// 1// 2// 3
迭代DOM集合
迭代 DOM 元素集合,比如一个对象:下面的例子演示给每一个 article 标签内的 p 标签添加一个 “read
” 类。
//注意:这只能在实现了NodeList.prototype[Symbol.iterator]的平台上运行let articleParagraphs = document.querySelectorAll("article > p");for (let paragraph of articleParagraphs) { paragraph.classList.add("read");}
关闭迭代器
对于for...of
的循环,可以由break
, throw continue
或return
终止。在这些情况下,迭代器关闭。
function* foo(){ yield 1; yield 2; yield 3;};for (let o of foo()) { console.log(o); break; // closes iterator, triggers return}
for…in与
for…of 的区别
无论是for...in
还是for...of
语句都是迭代一些东西。它们之间的主要区别在于它们的迭代方式。
语句以任意顺序遍历一个对象的除以外的属性。
(for ... in
是为遍历对象属性而构建的,不建议与数组一起使用,数组可以用Array.prototype.forEach()
和for ... of
,那么for ... in
的到底有什么用呢?它最常用的地方应该是用于调试,可以更方便的去检查对象属性(通过输出到控制台或其他方式)。尽管对于处理存储数据,数组更实用些,但是你在处理有key-value
数据(比如属性用作“键”),需要检查其中的任何键是否为某值的情况时,还是推荐用for ... in
。)
var obj = { a:1, b:2, c:3};for (var prop in obj) { console.log("obj." + prop + " = " + obj[prop]);}// Output:// "obj.a = 1"// "obj.b = 2"// "obj.c = 3"
for...of
语句遍历定义要迭代的数据。
以下示例显示了与一起使用时,for...of
循环和for...in
循环之间的区别。
Object.prototype.objCustom = function() { };Array.prototype.arrCustom = function() { };//每个对象将继承objCustom属性,并且作为Array的每个对象将继承arrCustom属性,因为将这些属性添加到Object.prototype和Array.prototype。//由于继承和原型链,对象iterable继承属性objCustom和arrCustom。let iterable = [3, 5, 7];iterable.foo = 'hello'; //对象iterable自己的属性//以原始插入顺序记录iterable对象的可枚举属性。它不记录数组元素3, 5, 7 或hello,因为这些不是枚举属性。//但是它记录了 数组索引 以及 arrCustom和objCustom。for (let i in iterable) { console.log(i); // logs 0, 1, 2, "foo", "arrCustom", "objCustom"}//使用hasOwnProperty() 来检查,如果找到的枚举属性是对象自己的(不是继承的)。如果是,该属性被记录。记录的属性是0, 1, 2和foo,//因为它们是自身的属性(不是继承的)。属性arrCustom和objCustom不会被记录,因为它们是继承的。for (let i in iterable) { if (iterable.hasOwnProperty(i)) { console.log(i); // logs 0, 1, 2, "foo" }}for (let i of iterable) { console.log(i); // logs 3, 5, 7}
如果你只要考虑对象本身的属性,而不是它的原型,那么使用 或执行 来确定某属性是否是对象本身的属性(也能使用)。
在 JavaScript 中,迭代器是一个对象,它定义一个序列,并在终止时可能返回一个返回值。 更具体地说,迭代器是通过使用 next()
方法实现 的任何一个对象,该方法返回具有两个属性的对象: value
,这是序列中的 next 值;和 done
,如果已经迭代到序列中的最后一个值,则它为 true
。如果 value
和 done
一起存在,则它是迭代器的返回值。
一旦创建,迭代器对象可以通过重复调用next()显式地迭代。 迭代一个迭代器被称为消耗了这个迭代器,因为它通常只能执行一次。 在产生终止值之后,对next()的额外调用应该继续返回{done:true}。
Javascript中最常见的迭代器是Array迭代器,它只是按顺序返回关联数组中的每个值。 虽然很容易想象所有迭代器都可以表示为数组,但事实并非如此。 数组必须完整分配,但迭代器仅在必要时使用,因此可以表示无限大小的序列,例如0和无穷大之间的整数范围。
这是一个可以做到这一点的例子。 它允许创建一个简单的范围迭代器,它定义了从开始(包括)到结束(独占)间隔步长的整数序列。 它的最终返回值是它创建的序列的大小,由变量iterationCount跟踪。
function makeRangeIterator(start = 0, end = Infinity, step = 1) { let nextIndex = start; let iterationCount = 0; const rangeIterator = { next: function() { let result; if (nextIndex < end) { result = { value: nextIndex, done: false } nextIndex += step; iterationCount++; return result; } return { value: iterationCount, done: true } } }; return rangeIterator;}
使用这个迭代器看起来像这样:
let it = makeRangeIterator(1, 10, 2);let result = it.next();while (!result.done) { console.log(result.value); // 1 3 5 7 9 result = it.next();}console.log("Iterated over sequence of size: ", result.value); // 5
工作原理:
a) 创建一个指针对象,指向当前数据结构的起始位置
b) 第一次调用对象的next 方法,指针自动指向数据结构的第一个成员
c) 接下来不断调用next 方法,指针一直往后移动,直到指向最后一个成员
d) 每调用next 方法返回一个包含value 和done 属性的对象
生成器函数是ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同
虽然自定义的迭代器是一个有用的工具,但由于需要显式地维护其内部状态,因此需要谨慎地创建。生成器函数提供了一个强大的选择:它允许你定义一个包含自有迭代算法的函数, 同时它可以自动维护自己的状态。 生成器函数使用 语法编写。 最初调用时,生成器函数不执行任何代码,而是返回一种称为Generator的迭代器。 通过调用生成器的下一个方法消耗值时,Generator函数将执行,直到遇到yield关键字。
可以根据需要多次调用该函数,并且每次都返回一个新的Generator,但每个Generator只能迭代一次。
我们现在可以调整上面的例子了。 此代码的行为是相同的,但实现更容易编写和读取。
function* makeRangeIterator(start = 0, end = Infinity, step = 1) { for (let i = start; i < end; i += step) { yield i; }}var a = makeRangeIterator(1,10,2)a.next() // {value: 1, done: false}a.next() // {value: 3, done: false}a.next() // {value: 5, done: false}a.next() // {value: 7, done: false}a.next() // {value: 9, done: false}a.next() // {value: undefined, done: true}
代码说明:
* 的位置没有限制
生成器函数返回的结果是迭代器对象,调用迭代器对象的next 方法可以得到yield 语句后的值
yield 相当于函数的暂停标记,也可以认为是函数的分隔符,每调用一次next方法,执行一段代码
转载地址:http://fkqgn.baihongyu.com/