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.for()
方法和 Symbol.keyFor()
方法从全局的symbol注册表设置和取得symbol。
在对象中查找Symbol属性
Object.getOwnPropertySymbols()
方法让你在查找一个给定对象的符号属性时返回一个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"的 symbol
Symbol.for("bar") === Symbol.for("bar"); // true,证明了上面说的
Symbol("bar") === Symbol("bar"); // false,Symbol() 函数每次都会返回新的一个 symbol
var sym = Symbol.for("mario");
sym.toString();
// "Symbol(mario)",mario 既是该 symbol 在 symbol 注册表中的键名,又是该 symbol 自身的描述字符串
Symbol.keyFor(sym)
:用来获取全局symbol 注册表中与某个 symbol 关联的键
// 创建一个全局 Symbol
var 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: false
console.log(symbol3.toString());
// expected output: "Symbol(foo)"
console.log(Symbol('foo') === Symbol('foo'));
// expected output: false
Symbols 在 for...in
迭代中不可枚举。另外,Object.getOwnPropertyNames()
不会返回 symbol 对象的属性,但是你能使用 Object.getOwnPropertySymbols()
得到它们。
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); // 2
console.log(objectSymbols) // [Symbol(a), Symbol(b)]
console.log(objectSymbols[0]) // Symbol(a)
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理且更强大。
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
语句在可迭代对象(包括 Array
,Map
,Set
,String
,TypedArray
,arguments 、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 元素集合,比如一个NodeList
对象:下面的例子演示给每一个 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
语句以任意顺序遍历一个对象的除Symbol以外的可枚举属性。
(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
语句遍历可迭代对象定义要迭代的数据。
以下示例显示了与Array
一起使用时,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
}
如果你只要考虑对象本身的属性,而不是它的原型,那么使用 getOwnPropertyNames()
或执行 hasOwnProperty()
来确定某属性是否是对象本身的属性(也能使用propertyIsEnumerable
)。
在 JavaScript 中,迭代器是一个对象,它定义一个序列,并在终止时可能返回一个返回值。 更具体地说,迭代器是通过使用 next()
方法实现 Iterator protocol 的任何一个对象,该方法返回具有两个属性的对象: 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 提供的一种异步编程解决方案,语法行为与传统函数完全不同
虽然自定义的迭代器是一个有用的工具,但由于需要显式地维护其内部状态,因此需要谨慎地创建。生成器函数提供了一个强大的选择:它允许你定义一个包含自有迭代算法的函数, 同时它可以自动维护自己的状态。 生成器函数使用 function*
语法编写。 最初调用时,生成器函数不执行任何代码,而是返回一种称为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方法,执行一段代码