专栏文章
速通JS Day1-7
科技·工程参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @mipgf7kd
- 此快照首次捕获于
- 2025/12/03 11:35 3 个月前
- 此快照最后确认于
- 2025/12/03 11:35 3 个月前
Learn From Scratch
开头使用
HTML"use strict"来使用ECMA5及以后的标准。
最好在每一个语句之后加上分号;。
使用let, const而非var!!!!!(let有高贵的块级作用域,没有声明提升 hoisting)
HTML里面可以引用外部脚本文件(下载到缓存),但是标签需要闭合:<script src="relative_or_absolute_path/script.js"></script>
数据类型:Number(±Infinity NaN)、BigInt
±(253-1)、String(格式字符串的使用反引号${})、Boolean、null(无、空、值未知)、undefined(未被赋值)、Object、Symbol。可以强制类型转换。
数字转换:| 值 | 变成…… |
|---|---|
undefined | NaN |
null | 0 |
true / false | 1 / 0 |
string | “按原样读取”字符串,两端的空白字符(空格、换行符 \n、制表符 \t 等)会被忽略。空字符串变成 0。转换出错则输出 NaN。 |
使用 typeof 来查询数据类型。注意的Feature:null->class function(本应为class)->function | |
交互方法:alert(msg), prompt(msg,[initial_input]), confirm(question) | |
| 只要有一个字符串,+变为concatenate,单元的+可以等同为转换为数字。优先级一元>二元(幂>乘除>加减) | |
字符串比较为Unicode字典序,数字比较显然,不同类型转化为Number再比较。严格相等/不等不转换类型。(严格运算为三个字符,不严格运算为两字符) | |
除了 null和undefined: | |
两者之间:null==undefined, null!==undefined(官方CP,null 只与 undefined 互等);在与其他东西比较的时候都会转化为Number。NaN与别的数字比较总会是False! | |
布尔转换(除了"\t0\n"之类的都相当于->Number->Boolean): |
- 数字
0、空字符串""、null、undefined和NaN都会被转换成false。因为它们被称为“假值(falsy)”。 - 其他值被转换为
true,所以它们被称为“真值(truthy)”。注意所有的对象(即使没有任何属性)都是true。 JS有三目运算符。&&,||均惰性求值(返回值为原始值而非Boolean)。a??b等价于(a !== null && a !== undefined) ? a : b。(null与undefined为未定义值) 循环类型:while/do.while/for(;;)/for.of(iterate_array)/for.in(iterate_attr)。循环可以加上tagtag:for(;;)做到多层的break/continue:break/continue tag;。 存在switch-case-[break]-default语句(case可以为变量),比较时执行===。 函数是第一公民。函数表达式可以认为是λ函数(但是可以有自己的名称,可以副职,匿名赋值时末位加分号,且运行时即刻创建/销毁)。函数可以作为参数。 函数在严格模式下具有块作用域。预处理时便直接声明(不用考虑先后顺序)。 好吧对不起箭头函数才是λ函数。
let func = (a, b) => a + b; //剪头后也可以插入代码块
Object
Basic
JS定义对象的语法非常反人类。(定义类的语法更是一坨,直到ES6才有)
类似Python,JS的key(必须是字符串,但是无空格的字符串使用的是自然写法)-value可以随时创建(对于实例直接赋值)与删除(
JAVASCRIPTdelete Instance.attr),你甚至可以使用多个单词的key(要求使用""包括,引用时使用Object["String"]——这个用法对于单个单词的key也适用,下文中user["name"]也是合法的)。
对象的属性名甚至可以使用保留字,或者是数字等等(在找key的情况下,所有[]里面的东西都会被自动转化为字符串,但你依然可以使用纯数字+方括号来索引)。let key = "double words"
let attr = prompt("Please input a string: ")
let user = {
name: "John",
age: 30, //建议最后的属性要带上逗号
[attr]: undefined, //这种带中括号的即 计算属性,可以直接算出来中括号中的值作为key
}
user[key] = true;
定义的时候可以使用属性值简写,有点像构造函数:
JAVASCRIPTfunction makeUser(name, age) {
return {
name, // 与 name: name 相同
age, // 与 age: age 相同
// ...
};
}
但是不一定要使用函数的形式,在普通的
JSlet语句中也可以这样用(别害怕,找不到的时候会给你undefined的)。判断是否有某个属性的方法为key in Object。
对于对象,可以使用for.in来遍历key:for(let key in Obj) {
alert(`key-value: ${key}-${Obj[key]}`);
}
排列顺序:整数属性会排序,剩下的按照定义顺序。
整数属性:先强制转换为Number再强制转换为String与原来相同的key("49"是,"+49","1.2"均不是)。
为了防止排序,你的key可以写成
"+Integer"的形式。这样就会被认为这是一个字符串。依然类似Python,任何mutable的东西(比如对象)都是引用赋值,共享地址。
因此,比较对象时,当且仅当地址相等,
JAVASCRIPT==, ===的结果才是True!(类似is)
如果你需要拷贝对象:一、使用for.in挨个赋值;二、Object.assign(target, src1, src2, ...)。
如果你想要deepclone:你得调库,或者递推地拷贝。
所有不可达的对象都会被自动垃圾回收。
方法定义:(在类继承时会有区别)user = {
sayHi: function() {
alert("Hello");
}
};
// 方法简写看起来更好,对吧?
let user = {
sayHi() { // 与 "sayHi: function(){...}" 一样
alert("Hello");
}
};
可选链:
value?.prop返回 value既不是null也不是undefined?value.prop:undefined。
而且还有 ?.(),?.[] 的变体。总结就是:只要左边是undefined就会直接短路。典中典之this
JS中任何东西都会有
JAVASCRIPTthis。对于类方法,this就是当前对象(当你要调用属性的时候必须使用this)。
所有的this都是在运行的时候即时算出来的。 没有this绑定。箭头函数没有this。let user = { name: "John" };
let admin = { name: "Admin" };
function sayHi() {
alert( this.name );
}
// 在两个对象中使用相同的函数
user.f = sayHi;
admin.f = sayHi;
// 这两个调用有不同的 this 值
// 函数内部的 "this" 是“点符号前面”的那个对象
user.f(); // John(this == user)
admin.f(); // Admin(this == admin)
admin['f'](); // Admin(使用点符号或方括号语法来访问这个方法,都没有关系。)
sayHi = function() {
alert(this);
}
sayHi(); // undefined(严格模式,若非严格模式得到全局对象),且若使用 this.name 会报错
如果想要链式调用(调用方法后产生一个可以使用的对象),你可以参考以下例子:
JSlet ladder = {
step: 0,
up() {
return {
let newObj;
Object.assign(newObj, this);
newObj.step++;
return newObj;
}//这种是不希望原来的函数更改
/* 如果希望更改;{this.step++; return this;} 可以省内存*/
},
showStep() {
alert(`step=${this.step}`);
return this;
}
}
幽默构造函数
构造函数并非定义在类中(或者是类方法)!所有构造函数都会是普通函数。
JAVASCRIPTfunction User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("Jack"); //new可能会迟到,但绝对不会缺席
//如果没有参数,可以省略括号(new User),但不建议这样写
alert(user.name); // Jack
alert(user.isAdmin); // false
//等价的写法: user = new function(){this.name = name;this.isAdmin = false;}
new.target:function User() {
alert(new.target);
}
// 不带 "new":
User(); // undefined
// 带 "new":
new User(); // function User { ... }(返回该函数)
// 顺带一提,new Obj(...)[...]的语法是合法的
这样可以提供一种不需要
JAVASCRIPTnew的神奇方法(库中常用,但是要慎用):function User(name) {
if (!new.target) { // 如果你没有通过 new 运行我
return new User(name); // ……我会给你添加 new
}
this.name = name;
}
let john = User("John"); // 将调用重定向到新用户
alert(john.name); // John
一般来说,构造函数是不需要
return。但是如果有return,当且仅当返回对象时返回此对象(并忽略this),否则返回原始类型时忽略return。Symbol
只有这个数据类型与
JAVASCRIPTString能够作为对象的键值。 并且它不会被自动转换成字符串,而是报错(只能.toString()或者.description)。
它跟 Ruby 的 Symbol 也是不一样的。尽管很像。let id1 = Symbol("id");
let id2 = Symbol("id");//key可以留空(无参数)
alert(id1 == id2); // false,这是对象
看起来没什么用,但是如果你使用第三方的对象的时候,使用Symbol可以防止不同组建之间对对象的修改不会互相影响。也即,Symbol属性为“隐藏”属性。
如果在定义对象时使用
Symbol,key值必须使用方括号。并且for.in不会遍历到Symbol。Object.keys(user) 也会忽略它们。但是Object.assign 会同时复制字符串和 symbol 属性。
如果你想要防止重复创建Symbol可以使用Symbol.for(key),在不存在此symbol时会创建一个,否则会返回已经创建的值。但是它依然不是Ruby。
Symbol.keyFor(sym)可以反向由符号查询值(仅限全局的Symbol)。上面代码框中的均不是全局量,直接查询会得到 undefined。因此此时只能用.toString()或者.description。注:有一个内建方法 Object.getOwnPropertySymbols(obj) 允许我们获取所有的 symbol。还有一个名为 Reflect.ownKeys(obj) 的方法可以返回一个对象的 所有 键,包括 symbol。但大多数库、内建方法和语法结构都没有使用这些方法。
Primitive Value
JS中没有重载运算符。(这语言是怎么存活到现在的?)
三种类型转换(hint):String(各类字符串操作)、Number(各种数学操作,包括含大于小于的比较)、Default(二元加法、
==等,通常除了Date对象都和"number"一致)。
为了进行转换,JavaScript 尝试查找并调用三个对象方法:- 调用
obj[Symbol.toPrimitive](hint)—— 带有 symbol 键Symbol.toPrimitive(系统 symbol)的方法,如果这个方法存在的话, - 否则,如果 hint 是
"string"—— 尝试调用obj.toString()(优先) 或obj.valueOf(),无论哪个存在。 - 否则,如果 hint 是
"number"或"default"—— 尝试调用obj.valueOf()(优先) 或obj.toString(),无论哪个存在。
obj[Symbol.toPrimitive] = function(hint) {
// 这里是将此对象转换为原始值的代码
// 它必须返回一个原始值
// hint = "string"、"number" 或 "default" 中的一个
}
如果
toString 或 valueOf 返回了一个对象,那么返回值会被忽略(和这里没有方法的时候相同)。
默认情况下,普通对象具有 toString 和 valueOf 方法:toString方法返回一个字符串"[object Object]"。valueOf方法返回对象自身。
Attributes
对象属性除了value,还有三个标志:
writable— 如果为true,则值可以被修改,否则它是只可读的。enumerable— 如果为true,则会被在循环中列出(for.in),否则不会被列出。configurable— 如果为true,则此属性可以被删除,这些特性(列表中的三者)也可以被修改,否则不可以。
let user = {
name: "John"
};
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');//查成分
alert( JSON.stringify(descriptor, null, 2 ) );
/* 属性描述符:
{
"value": "John",
"writable": true,
"enumerable": true,
"configurable": true
}
*/
修改属性标志:使用
Object.defineProperty(obj, propertyName, descriptor)。最后一个参数为“属性描述符”对象(即上文代码块中getOwnPropertyDescriptor返回的格式)。如果属性描述符里面没有某种flag则默认为false。当属性不存在时会使用descriptor创建参数,否则会更新其值。
一般默认的内置类型转换等方法的enumerable为false(无法使用for.in或Object.keys()),但是如果显性重新定义后则enumerable变为True。
一次修改多个:Object.defineProperties(obj, { prop1: descriptor1, prop2: descriptor2 , ...});
一次获得所有属性描述符:Object.getOwnPropertyDescriptor(obj)。Accessor
有点Python的
JAVASCRIPT@property内味了。let obj = {
get propName() {/* 当读取 obj.propName 时,getter 起作用*/},
set propName(value) {/* 当执行 obj.propName = value 操作时,setter 起作用 */}
};
相应地,访问器属性描述符没有
JAVASCRIPTwritable,取而代之的是无参数函数get()和单参数函数set(value)。一个属性也要么是数据类型(有value)要么是访问器类型(get/set)。Object.defineProperty(user, 'fullName', {
get() {
return `${this.name} ${this.surname}`;
},
set(value) {
[this.name, this.surname] = value.split(" ");
}
});
JAVASCRIPTfunction User(name, birthday) {
this.name = name;
this.birthday = birthday;
// 年龄是根据当前日期和生日计算得出的
Object.defineProperty(this, "age", { //构造函数内也可以直接这么写!
get() {
let todayYear = new Date().getFullYear();
return todayYear - this.birthday.getFullYear();
}
});
}
let john = new User("John", new Date(1992, 6, 1));
alert( john.birthday ); // birthday 是可访问的
alert( john.age ); // ……age 也是可访问的
Prototype Inheritance
你没有看错,在定义类之前我们就有原型和继承了。
原型使用隐藏属性
[[Protytype]]实现。虽然你无法访问,但可以使用obj.__proto__来设置(它是这个隐藏属性的getter/setter)。在当前对象中找不到的属性会自动从原型中寻找。可以多层继承。但是原型只能有一个。
this属性永远是.前面的东西!!!继承仅仅影响方法,不会继承对象状态。
for.in会遍历继承的属性。obj.hasOwnProperty(key)返回是否是自己的属性(非继承)。笑点解析:这个函数本身就是继承的。
几乎其他所有的key/value获取方法都会忽略继承的属性*(包括Object.keys/values/entries(obj)与delete等)*。当然你重新赋值了一遍就相当于不是继承的了。注意:绑定的不是某个名字,而是当时与名字绑定的对象。
如果想要用构造函数来绑定原型,可以给
F.prototype属性赋值(不知道函数属性可以看[[#函数对象]]),之后使用new操作构造的对象的.__proto__就会与F.prototype共享一个对象。
默认的函数原型为F.prototype = { constructor: F };。因此,如果你想要保留constructor属性,你可以重新赋值或者只给prototype赋属性值。关于原型的进一步思考:
我们知道默认的
object.toString()返回的值为"[object Object]",但是它来自哪里?答案是来自系统内置的构造函数Object(),并且Object.prototype存储着属性toString()。
同理,其他对象(Array,Date,Function等等)都在prototype上面挂载了方法。而任意的arr/date/func.__proto__.__proto__ === Object.prototype。 问题来了,原始值(参见后文中[[#Primitive]])没有对象而只有对象**包装器**。也就是说访问原始值的属性/方法时,临时包装器会使用内置的构造器String, Number, Boolean等被创建。因此你可以通过String.prototype来给每个字符串来加个新的对象。(对其它类型同理,但是**最好不要这样做!**)你只能在 polyfilling 的时候这样做。不过还有另一种用法:方法借用。 当然,现在也不建议使用直接给proto`来设置和读取原型了(这一般只适用于浏览器,虽然对于服务器环境大多数也能用),建议使用:- Object.getPrototypeOf(obj) —— 返回对象
obj的[[Prototype]]。 - Object.setPrototypeOf(obj, proto) —— 将对象
obj的[[Prototype]]设置为proto。__proto__不被反对的唯一的用法是在创建新对象时,将其用作属性:{ __proto__: ... }。 虽然,也有一种特殊的方法: - Object.create(proto, [descriptors]) —— 利用给定的
proto作为[[Prototype]]和可选的属性描述来创建一个空对象。(时刻记得属性描述器默认为false!) 于是我们得到了真正的对象克隆的完全体:
let clone = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
);
Types
超级比较相等的方法:
SameValueZero(x,y)参见ecma。Primitive
原始类型也有方法(不包括
null和undefined),但是为了尽可能保证原始的数据类型尽可能轻量,实现方法为“对象包装器”。注意对象包装器不能写入数据。Number:均是符合IEEE-754规范的浮点数,/为直接相除(没有整除功能)。注意123456..toString(base) === (123456).toString(base)中第一个点会被解释为小数点。 经典.1+.2!=.3,这时舍入方法为Math.ceil/floor/round/trunc(num)或者num.toFixed(digits_after_dots)(注意它返回的是一个字符串,结果与round相同)。 为了防止精度消失,你可以将+6.35.toFixed(1)等转换为Math.round(6.35 * 10) / 10。 两个函数:isFinite(num)与isNaN(num),求值时会先转换为Number对象。 (不能使用==,因为NaN !== NaN!)
有一个特殊的内建方法Object.is,它类似于===一样对值进行比较,但它对于两种边缘情况更可靠:
- 它适用于
NaN:Object.is(NaN, NaN) === true,这是件好事。- 值
0和-0是不同的:Object.is(0, -0) === false,从技术上讲这是对的,因为在内部,数字的符号位可能会不同,即使其他所有位均为零。 在所有其他情况下,Object.is(a, b)与a === b相同。
parseInt(str, [radix]), parseFloat(str, [radix])从第一个字符开始读取字符,直到无法被解析为数字为止,返回一个Number。可以读取带单位的值,例如"12px"。-
Bigint:定义:const bigint = 1234n | Bigint(1234),除法变为取整。 不能直接与Number进行加减乘除,必须要强制类型转换;但是可以进行数值比较(2n > 1, 2n == 2, 2n !== 2)。转换为Number不能使用一元加法。 -
String:均是以UTF-16存储,反引号可以来定义多行字符串。
|---|---|
|
\uXXXX|以 UTF-16 编码的十六进制代码 XXXX 的 Unicode 字符,例如 \u00A9 —— 是版权符号 © 的 Unicode。它必须正好是 4 个十六进制数字。|\u{X…XXXXXX}(1 到 6 个十六进制字符) | 具有给定 UTF-32 编码的 Unicode 符号。一些罕见的字符用两个 Unicode 符号编码,占用 4 个字节。这样我们就可以插入长代码了。 |
|---|---|
查询长度:str.length;索引直接使用方括号(没有负数索引,必须得要查询常数),返回长度为1的字符串,找不到返回undefined;为不可变类型,不能改变索引处的值。 | |
常见属性:str.toUpperCase(), str.toLowerCase();str.indexOf/lastIndexOf(substr, [pos])从pos开始查找,找不到返回-1,判断不是-1的技巧:按位非~;如果只需要查找是否存在可以str.includes/startsWith/endsWith(substr, [pos])(返回布尔值);str.split(char)/arr.join(char)。 | |
| 方法 | 选择方式…… |
| --- | --- |
slice(start[, end]) | 从 start 到 end(不含 end) |
substring(start[, end]) | 从 start 到 end(不含 end,若前比后大则交换) |
substr(start[, length]) | 从 start 开始获取长为 length 的字符串 |
str.codePointAt(pos)与String.fromCodePoint(code)实现UTF码与字符的互相转换。
有时候为了恰当地比较一些有帽子的字符,可以使用str.localeCompare(str2),str靠前返回-1,靠后返回+1,相同返回0。(还可以加额外的参数来指定比较形式,参见docs);str.trim()可以删除字符串前后的空格。Array:可以new Array()或者[]定义,性质和Python很相似。 依然不能使用负数索引,如果要使用得要array.at(-1)或者使用array.length(警告:这返回最大索引值加一,而且你甚至可以直接手动修改这个属性:增加时会多出一堆undefined,减小时数组会被截断,因此清空数组可以arr.length=0)。 你可以把JS中的数组看成一个deque,push/pop操作队尾,shift/unshift操作队头。push/shift可以一次加入多个元素,保持参数的顺序;pop/unshift无参数有返回值。 所有的数组都是对象,方括号值也与对象的用法相同,但是最好不要在任意索引处赋值/添加额外属性,否则程序优化会失效。 迭代可以使用for(;;)或者for.of(只返回value不返回key,对于类数组对象(类似Python中可迭代对象)有优化且只返回数字索引值,不建议用for.in)。 数组只有toString没有另外两个转换方式。 超级数组操作:arr.splice(start[, deleteCount, elem1, ..., elemN])从start(支持负数索引)删除deleteCount个元素并加入elem。 简单一些的修改:arr.slice([start][, end])(无参数时返回副本);arr.concat(arr1,...)链接数组或者元素;arr.forEach(function(item, index, array){})为依次执行(如果函数参数少于三个自动取前面的;函数返回值会被丢弃)。 查找:arr.indexOf(item[, from])—— 从索引from开始搜索item,如果找到则返回索引,否则返回-1;arr.includes(item[, from])则返回Boolean。判断方法均为===(但是方法includes可以正确的处理NaN,参见SameValue(x,y))。如果要找满足特定条件的元素,使用arr.find/filter(function(item, index, array){})(存在返回item/所有item的集合,不存在返回undefined)或者.findIndex/findLastIndex存在返回索引,不存在返回-1。 转换:arr.map(function(item, index, array){})来分别代入返回数组;arr.sort([fn])在原地址对数组进行排序(默认转化为字符串后按字典序排序,fn在大于/小于/等于时返回正/负/0,最终从小到大排序,比如arr.sort((a, b)=>a-b));arr.reverse()原位颠倒;arr.reduce/reduceRight(function(accumulator, item, index, array){}, [initial_sum])为整个数组返回一个值(从左到右/右到左)。Array是不能用typeof的(会返回object),但能够使用Array.isArray(val)。
几乎所有调用函数的数组方法 —— 比如find,filter,map,除了sort是一个特例,都接受一个可选的附加参数thisArg。thisArg参数的值在func中变为this。
Iterable
比方说Python中的
JAVASCRIPTrange就是可迭代的(以及yield等等)。只有可迭代对象才能用for.of。let range = {
from: 1,
to: 5
};
// 1. for..of 调用首先会调用这个:
range[Symbol.iterator] = function() {
// ……它返回迭代器对象(iterator object):
// 2. 接下来,for..of 仅与下面的迭代器对象一起工作,要求它提供下一个值
return {
current: this.from,
last: this.to,
// 3. next() 在 for..of 的每一轮循环迭代中被调用
next() {
// 4. 它将会返回 {done:.., value :...} 格式的对象
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
};
// 现在它可以运行了!
for (let num of range) {
alert(num); // 1, 然后是 2, 3, 4, 5
}
for.of等价于以下显式调用迭代器的过程:let str = "Hello";
// 和 for..of 做相同的事
// for (let char of str) alert(char);
let iterator = str[Symbol.iterator]();
while (true) {
let result = iterator.next();
if (result.done) break;
alert(result.value); // 一个接一个地输出字符
}
- Iterable 是实现了
Symbol.iterator方法的对象。 - Array-like 是有索引和
length属性的对象,所以它们看起来很像数组。Array.from(obj[, mapFn, thisArg])可以接受可迭代对象或者类数组并得到一个真的数组(如果有mapFn可以额外映射一次)!这下终于可以处理双字节的字符了(UTF16):
let str = '𝒳😂';
// 将 str 拆分为字符数组
let chars = Array.from(str);
alert(chars[0]); // 𝒳
alert(chars[1]); // 😂
alert(chars.length); // 2
JS中还有
Map和Set(我嘞个STL啊)。两者都可以使用for.of迭代,顺序与插入顺序相同 。
Map可以使用任何类型的key。不要使用方括号索引,不然这个美好的特性就用不上了。你可以使用对象作为key(如果使用普通的方括号索引,两个字符串都会被.toString()变成"[object Object]")。new Map()—— 创建 map,参数可以替换为一个两列的数组或其他可迭代对象。map.set(key, value)—— 根据键存储值,返回map本身,因此可以链式调用(叠加多个.set(a, b))。map.get(key)—— 根据键来返回值,如果map中不存在对应的key,则返回undefined。map.has(key)—— 如果key存在则返回true,否则返回false。map.delete(key)—— 删除指定键的值。map.clear()—— 清空 map。map.size—— 返回当前元素个数。map.keys()—— 遍历并返回一个包含所有键的可迭代对象。迭代的顺序与插入值的顺序相同。map.values()—— 遍历并返回一个包含所有值的可迭代对象,map.entries()—— 遍历并返回一个包含所有实体[key, value]的可迭代对象,for..of在默认情况下使用的就是这个。map.forEach(function(value, key, map){...})与Array的forEach相似。 如果我们想从一个已有的普通对象(plain object)来创建一个Map,那么我们可以使用内建方法 Object.entries(obj),该方法返回对象的键/值对数组,该数组格式完全按照Map所需的格式。Object.fromEntries方法的作用是相反的:给定一个具有[key, value]键值对的数组,它会根据给定数组创建一个对象。Set相当于只有value没有key。new Set(iterable)—— 创建一个set,如果提供了一个iterable对象(通常是数组),将会从数组里面复制值到set中。set.add(value)—— 添加一个值,返回 set 本身。重复添加同一个值不会改变set。set.delete(value)—— 删除值,如果value在这个方法调用的时候存在则返回true,否则返回false。set.has(value)—— 如果value在 set 中,返回true,否则返回false。set.clear()—— 清空 set。set.size—— 返回元素个数。set.keys()—— 遍历并返回一个包含所有值的可迭代对象,set.values()—— 与set.keys()作用相同,这是为了兼容Map,set.entries()—— 遍历并返回一个包含所有的实体[value, value]的可迭代对象,它的存在也是为了兼容Map。set.forEach(function(value, valueAgain, map){...})——注意参数的变化。 还有一种查询key-value的方法:Object.keys/values/entries(obj)。三者均应该返回数组而不是上文中类似的可迭代对象(历史遗留问题)。
for.in循环会忽略Symbol属性,Object.keys/values/entries(obj)也是。 如果你想要Symbol类型的key,可以使用Object.getOwnPropertySymbols(仅包含Symbol的键)或者Reflect.ownKeys(obj)(返回 所有 键)。
JS中更加神人的
JAVASCRIPTWeakMap和WeakSet:
WeakMap的key必须是对象而不能是原始值。并且,其中的作为Key的对象必须存在其他引用,否则会被垃圾回收!幽默弱引用。let john = { name: "John" };
let weakMap = new WeakMap();
weakMap.set(john, "...");
john = null; // 覆盖引用
// john 被从内存中删除了!
WeakMap 不支持迭代以及 keys(),values() 和 entries() 方法(因为内存回收是引擎决定何时执行的,因此这些方法的结果无法确定)。只有以下的方法:weakMap.get(key)weakMap.set(key, value)weakMap.delete(key)weakMap.has(key)在多个组件时,WeakMap可以方便地实现缓存的功能。WeakSet同理,只能add, has, delete。不能迭代。
Generator
喜欢
JAVASCRIPTyield吗?那么你就要使用function*。function* generateSequence() {
yield 1;
yield 2;
return 3;
}
// "generator function" 创建了一个 "generator object"
let generator = generateSequence();
alert(generator); // [object Generator]
赋值的时候,迭代器里的代码还没有真正运行。
当然每个迭代器都是可以调用
JAVASCRIPT.next()的,与前文相仿,里面会有value与done两个属性。
使用for.of循环时不会遍历最后的return值,你可以把它改成yield。
我们可以把上文中某个例子改成更加紧凑的形式:let range = {
from: 1,
to: 5,
*[Symbol.iterator]() { // [Symbol.iterator]: function*() 的简写形式
for(let value = this.from; value <= this.to; value++) {
yield value;
}
}
};
alert( [...range] ); // 1,2,3,4,5
套娃:
JAVASCRIPTfunction* generateSequence(start, end) {
for (let i = start; i <= end; i++) yield i;
}
function* generatePasswordCodes() {
yield* generateSequence(48, 57);// 0..9
yield* generateSequence(65, 90);// A..Z
yield* generateSequence(97, 122);// a..z
}
generator不是一个纯输出的结构,你当然可以给它整上输入(
JAVASCRIPTgenerator.next(input))。function* gen() {
// 向外部代码传递一个问题并等待答案
let result = yield "2 + 2 = ?"; // (*)
alert(result);
}
let generator = gen();
let question = generator.next().value; // <-- yield 返回的 value
generator.next(4); // --> 将结果传递到 generator 中
你甚至还能给generator报错(
JAVASCRIPTgenerator.throw(err)):function* gen() {
try {
let result = yield "2 + 2 = ?"; // (1)
alert("The execution does not reach here, because the exception is thrown above");
} catch(e) {
alert(e); // 显示这个 error
}
}
let generator = gen();
let question = generator.next().value;
generator.throw(new Error("The answer is not found in my database")); // (2)
如果generator没有接住这个error,它会从函数中掉出来,你可以在
generator.throw外面在套上一层try.catch来解决。
generator.return(value)可以使当前迭代器变成{value, done: true}。
与async一起的用法参见后文。Assignment
Python:这还用学?
JAVASCRIPTlet [firstName, surname] = arr;
//let firstName = arr[0], surname = arr[1];
let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
alert( title ); // Consul
let [a, b, c] = "abc"; // ["a", "b", "c"]
let [one, two, three] = new Set([1, 2, 3]); //这两者都是迭代器发力了
let user = {
name: "John",
age: 30
};
for (let [key, value] of Object.entries(user)) {
alert(`${key}:${value}`); // name:John, then age:30
}
let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
// rest 是包含从第三项开始的其余数组项的数组
alert(rest[0]); // Consul
alert(rest[1]); // of the Roman Republic
alert(rest.length); // 2
let [firstName, surname] = [];
alert(firstName); // undefined
alert(surname); // undefined
// 只会提示输入姓氏,能够被赋值的对象的缺省值会被忽略
let [name = prompt('name?'), surname = prompt('surname?')] = ["Julius"];
alert(name); // Julius(来自数组)
alert(surname); // 你输入的值
let {height, width, title} = { title: "Menu", height: 200, width: 100 }
alert(title); // Menu
alert(width); // 100
alert(height); // 200
let options = {
title: "Menu",
width: 100,
height: 200
};
// { sourceProperty: targetVariable }
let {width: w /*=prompt("width? ") 在能够赋值时被忽略*/, height: h, title} = options; //这个语法可能和直觉相反
let {title, ...rest} = options;
// 现在 title="Menu", rest={height: 200, width: 100}
注意:在不使用let时,引擎可能会把大括号认为是代码块;为了避免这种情况,要在语句的两端加上()。 以上这些代码可以嵌套(例子略)。
智能传参:感觉不如Python。(注意,调用函数的时候参数不能留空,而数组可以。)
JAVASCRIPTlet options = {
title: "My menu",
items: ["Item1", "Item2"]
};
function showMenu({
title = "Untitled",
width: w = 100, // width goes to w
height: h = 200, // height goes to h
items: [item1, item2] // items first element goes to item1, second to item2
} = {}) { //You can use showMenu() instead of showMenu({})
alert( `${title} ${w} ${h}` ); // My Menu 100 200
alert( item1 ); // Item1
alert( item2 ); // Item2
}
showMenu(options);
JSON
对象转换为JSON:
JAVASCRIPTJSON.stringify(object);JSON转换为对象:JSON.parse(json)。
注意:JSON中所有Key被改写为有双引号的字符串。
JSON支持的类型:Object Array String Number Boolean null
JSON不包含:函数/方法、Symbol/undefined的key/value。不能存在循环引用。
完整版:JSON.stringify(value[, replacer, space]),replacer代表要编码的属性数组或者映射函数,space表示格式化所需要的空格数量。let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: [{name: "John"}, {name: "Alice"}],
place: room // meetup 引用了 room
};
room.occupiedBy = meetup; // room 引用了 meetup
alert( JSON.stringify(meetup, ['title', 'participants']) );
// {"title":"Conference","participants":[{},{}]}
alert( JSON.stringify(meetup, ['title', 'participants', 'place', 'name', 'number']) );
/*
{
"title":"Conference",
"participants":[{"name":"John"},{"name":"Alice"}],
"place":{"number":23}
}
alert( JSON.stringify(meetup, function replacer(key, value) {
alert(`${key}: ${value}`);
return (key == 'occupiedBy') ? undefined : value;
}));
/* key:value pairs that come to replacer:
: [object Object]
title: Conference
participants: [object Object],[object Object]
0: [object Object]
name: John
1: [object Object]
name: Alice
place: [object Object]
number: 23
occupiedBy: [object Object]
*/
可以给对象自定义
toJSON()方法。
JSON.parse(str, [reviver])的可选参数对每个(key, value)调用并转换,返回value。
注意:以上所用方法对于嵌套的元素均会起效果!Function
可变参数
JS中原则上你可以传入任意个数的参数,当形参对于实参时多出部分变成
JAVASCRIPTundefined,反之则会忽略多余的实参。
如果想要收集多余参数,可以使用...args(可以使用其他名称)。一定要把它放在最后一位。args会成为一个数组。
有一个名为 arguments 的特殊类数组对象可以在函数中被访问,该对象以参数在参数列表中的索引作为键,存储所有参数。箭头函数没有arguments,与this类似。(它访问的arguments相当于外部函数的值。)
如果想要将数组/可迭代对象(不能直接用于类数组,必须得要Array.from())进行解包操作,依然可以在参数中写成...arr(此时当然不一定放在最后)。可以发现如果将...替换为*,以上的两个操作将会变为我们在Python中熟悉的形式。
因此,我们又获得了浅拷贝的另一种形式[...arr],{...obj}。
一个高级的柯里化实现:function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
}
}
};
}
作用域
let与const均为块作用域,无声明提升。JS中存在闭包。每次调用函数都会产生一个全新的词法环境。这块跟Python几乎一模一样(把所谓Lexical enviroment替换为Frame就一样了)。参见这一章的内容(强烈建议把习题都做一遍)。
JS中的全局对象为globalThis。在浏览器中,它叫做window;在Node.js中它叫做global,在别的环境中可能会有别名。
全局对象的属性/方法可以直接调用,省略globalThis.;使用var声明的全局变量会变为全局对象的属性(不要这么做!)。全局函数声明也会有同样的效果(函数表达式、箭头函数都不是函数声明)。你也可以直接编辑全局变量的某些属性,来等效地让它变为全局的。函数对象
函数的名字可以使用
JAVASCRIPTfunc.name(没有括号!)来访问,即使在创建时使用函数表达式赋值依然能够正确地识别(上下文命名)。如果上下文命名失败,name=""。func.length返回形参个数(不包括可变参数)。
在函数内部也可以定义属性(请与变量区分)。你可以用它来部分地代替闭包(如果你想要某个参数能被外部代码修改那就使用函数属性,否则使用闭包)。function sayHi() {
alert("Hi");
// 计算调用次数
sayHi.counter++;
}
sayHi.counter = 0; // 初始值
sayHi(); // Hi
sayHi(); // Hi
alert( `Called ${sayHi.counter} times` ); // Called 2 times
对于命名的函数表达式(尽管有名字但依旧不是函数声明)有两个特性:在函数体内能够递归调用;在函数外其名称不可见。
正如“函数对象”其名,函数也可以使用
JAVASCRIPTnew来构造:let func = new Function ([arg1, arg2, ...argN], functionBody);
其中的所有参数均为字符串。
JAVASCRIPTlet sum = new Function('a', 'b', 'return a + b');
//let sum = new Function('a , b', 'return a+b');
alert( sum(1, 2) ); // 3
这种情况常见于要从服务器接受代码来动态编译函数。注意此时得到的函数的词法环境为全局变量!
装饰器种种
并没有美丽的
JAVASCRIPT@语法,你只能直接调用来函数装饰。当装饰到含有this的函数时,可以使用func.call(context, ...args)方法,第一个参数是为this:func(1, 2, 3); //this == globalThis
func.call(obj, 1, 2, 3); //this == obj
/*
function hash() {
return [].join.call(arguments); //方法借用
}
*/
如果你不想传入参数列表而是一个类数组,那么将
JAVASCRIPTcall变为apply即可。
如果你只想要得到一个绑定了this的函数,请使用func.bind(context)(一个函数只能被绑定一次)。bind结束后得到的是另一个对象,它不会保留原来加上的函数属性。let bound = func.bind(context, [arg1], [arg2], ...);
是的,完整版的
JAVASCRIPTfunc.bind还可以绑定参数。这样比使用闭包来调用方便多了。但是如果你只想要绑定参数,你确实可以自定义一个东西:function partial(func, ...argsBound) {
return function(...args) { // (*)
return func.call(this, ...argsBound, ...args);
}
}
=>
箭头函数没有
this,没有arguments,它全部从外界获取。箭头函数不会离开当前的上下文。
同理,箭头函数不能作为构造器,不能将其用到new后面。它也没有super。Class
基础
笑点解析:继承都讲完了才出现类。
JAVASCRIPTclass MyClass {
// class 方法
constructor() { ... }
method1() { ... }
method2() { ... }
method3() { ... }
...
}
let class = new MyClass();
alert(typeof User); // function
类方法之间没有括号!
正如上文,类是一种函数(令人忍俊不禁)。也就是说,
JAVASCRIPTMyClass === MyClass.prototype.constructor;上面的constructor与每个method为MyClass.prototype的方法。(也就是说,每个创建的对象相当于继承了类。)
那么,为什么我不直接使用普通的构造函数呢?通过 class 创建的函数具有特殊的内部属性标记 [[IsClassConstructor]]: true。因此,它与手动创建并不完全相同。例如,与普通函数不同,必须使用 new 来调用它;类有自己的toString;类方法不可枚举(不能使用for.in);等等。
当然类也有类表达式之说,类表达式也可以有自己的姓名。// “命名类表达式(Named Class Expression)”
// (规范中没有这样的术语,但是它和命名函数表达式类似)
let User = class MyClass {
sayHi() {
alert(MyClass); // MyClass 这个名字仅在类内部可见
}
};
new User().sayHi(); // 正常运行,显示 MyClass 中定义的内容
alert(MyClass); // error,MyClass 在外部不可见
同理,类也可以使用getter.setter等等,或者使用计算属性(中括号内部的东西)。
所谓类字段,就是在定义类的时候加入属性(这个在C/C++看起来这么显而易见的东西居然在最近才放入标准,可能需要polyfill)。
JAVASCRIPTclass User {
name = "John"; //孩子们很多旧版本都不支持我
//你甚至可以使用 name = prompt("Name, please?", "John") 这种语句。
sayHi() {
alert(`Hello, ${this.name}!`);
}
}
new User().sayHi(); // Hello, John!
所有类字段会挂在实例对象而非原型上。
回忆:在某些时候使用类方法做其它函数的参数会出现
JAVASCRIPTthis丢失的问题:class Button {
constructor(value) {
this.value = value;
}
click() {
alert(this.value);
}
}
let button = new Button("hello");
setTimeout(button.click, 1000); // undefined
这时候有三种方法:
- 传递一个包装函数,例如
setTimeout(() => button.click(), 1000)。 - 将方法绑定到对象(
func.bind),例如在 constructor 中。 - 在类中写下
click = () => { alert(this.value) }。这对于事件监听非常有用。
继承
JAVASCRIPTclass Animal {
constructor(name) {
this.speed = 0;
this.name = name;
}
run(speed) {
this.speed = speed;
alert(`${this.name} runs with speed ${this.speed}.`);
}
stop() {
this.speed = 0;
alert(`${this.name} stands still.`);
}
}
let animal = new Animal("My animal");
class Rabbit extends Animal {
hide() {
alert(`${this.name} hides!`);
}
}
let rabbit = new Rabbit("White Rabbit");
rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
此时
JAVASCRIPTRabbit.prototype.[[prototype]] === Animal.prototype。
注意,extends之后可以写任意表达式:function f(phrase) {
return class {
sayHi() { alert(phrase); }
};
}
class User extends f("Hello") {}
new User().sayHi(); // Hello
找爹:使用
super关键字来更加灵活地重写部分属性。(箭头函数依然没有super,只会从外部找。)- 执行
super.method(...)来调用一个父类方法。 - 执行
super(...)来调用一个父类 constructor(只能在我们的 constructor 中)。
class Animal {
constructor(name) {
this.speed = 0;
this.name = name;
}
run(speed) {
this.speed = speed;
alert(`${this.name} runs with speed ${this.speed}.`);
}
stop() {
this.speed = 0;
alert(`${this.name} stands still.`);
}
}
class Rabbit extends Animal {
hide() {
alert(`${this.name} hides!`);
}
stop() {
super.stop(); // 调用父类的 stop
this.hide(); // 然后 hide
}
}
let rabbit = new Rabbit("White Rabbit");
rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.stop(); // White Rabbit stands still. White Rabbit hides!
哦天呐,我们还没有重写构造器呢!默认情况下如果没写构造器那就和
JAVASCRIPTsuper产生一样的行为。让我们来试试:class Animal {
constructor(name) {
this.speed = 0;
this.name = name;
}
// ...
}
class Rabbit extends Animal {
constructor(name, earLength) {
this.speed = 0;
this.name = name;
this.earLength = earLength;
}
// ...
}
// 不工作!
let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined.
为什么报错:一句话:继承类的constructor必须调用
super(...),并且一定要在使用this之前调用!
为什么会这样?因为继承类的构造函数会存在特殊的隐藏标签[[Constructorkind]]:"derived"。因此它在new的时候行为会发生改变:- 当通过
new执行一个常规函数时,它将创建一个空对象,并将这个空对象赋值给this。 - 但是当继承的 constructor 执行时,它不会执行此操作。它期望父类的 constructor 来完成这项工作。
所以才会出现上文中的那些行为。正确写法应该是把上文中
constructor的前两行改为super(name)。 更加诡异的是对于类字段的继承。父类构造器总会使用自己的字段值,而非子类重写的那一个。然而类方法使用的是子类的值。(例子参见此处) 问题来了,这是为什么呢?原因在于字段初始化的顺序。类字段是这样初始化的: - 对于基类(还未继承任何东西的那种),在构造函数调用前初始化。
- 对于派生类,在
super()后立刻初始化。 所以,字段(常规属性)与方法的区别就在于此。后文会详细介绍super究竟是怎样一个屎山。
关于super的一切
你可能觉得调用
JAVASCRIPTsuper的方法只需要将其替换为this.__proto__就可以了:let animal = {
name: "Animal",
eat() {
alert(`${this.name} eats.`);
}
};
let rabbit = {
__proto__: animal,
eat() {
// ...bounce around rabbit-style and call parent (animal) method
this.__proto__.eat.call(this); // (*)
}
};
let longEar = {
__proto__: rabbit,
eat() {
// ...do something with long ears and call parent (rabbit) method
this.__proto__.eat.call(this); // (**)
}
};
longEar.eat(); // Error: Maximum call stack size exceeded
Oops,死循环。为了解决这种野蛮的错误,JS添加了特殊内部属性
JAVASCRIPT[[HomeObject]]。一个类/对象的方法的[[HomeObject]]就是该类/对象。于是调用super的时候就会从它的[[HomeObject]]中寻找。
很遗憾,[[HomeObject]]是永久绑定的,无法被更改。let animal = {
sayHi() {
alert(`I'm an animal`);
}
};
// rabbit 继承自 animal
let rabbit = {
__proto__: animal,
sayHi() {
super.sayHi();
}
};
let plant = {
sayHi() {
alert("I'm a plant");
}
};
// tree 继承自 plant
let tree = {
__proto__: plant,
sayHi: rabbit.sayHi // (*)
};
tree.sayHi(); // I'm an animal (?!?)
这时候方法借用时,
super无法动态绑定。这使我们更加清晰地看出:
方法,不是函数属性!!!(方法有[[HomeObject]]而函数没有。)
所以,不要借用有super的函数。static
想你了C++。
JAVASCRIPTstatic标签中调用的this就是类构造器。
static方法都是挂载在Class而非Class.Prototype。
通常,静态方法用于实现属于整个类,但不属于该类任何特定对象的函数。class Article {
constructor(title, date) {
this.title = title;
this.date = date;
}
static compare(articleA, articleB) {
return articleA.date - articleB.date;
}
}
// 用法
let articles = [
new Article("HTML", new Date(2019, 1, 1)),
new Article("CSS", new Date(2019, 0, 1)),
new Article("JavaScript", new Date(2019, 11, 1))
];
articles.sort(Article.compare);
alert( articles[0].title ); // CSS
另一个例子:
JAVASCRIPTclass Article {
constructor(title, date) {
this.title = title;
this.date = date;
}
static createTodays() {
// 记住 this = Article
return new this("Today's digest", new Date());
}
}
let article = Article.createTodays();
alert( article.title ); // Today's digest
注意:静态方法不适用于单个对象。
当然你可以使用静态的类属性(很遗憾这依然是一个最近新增的feature)。静态属性可以继承(当然是在类与类之间而不是方法与方法之间)。
public, private, protected
public:不需要实现。
protected:没有语法层面的强制实现。我们一般约定单下划线的开头的类/对象属性/方法只能内部访问,读写操作使用对象方法或get/set实现。很显然这是可以继承的。
private:最近新添的feature允许私有属性与方法以#开头。调用的时候依然需要加上#,因此它们不会与其它属性字段发生冲突。它们不能使用obj.#a或obj["#a"]等方式来访问。大部分情况下你只能使用polyfills实现。内建的许多类(
JAVASCRIPTArray, Map等)都是可以扩展的。// 给 PowerArray 新增了一个方法(可以增加更多)
class PowerArray extends Array {
isEmpty() {
return this.length === 0;
}
}
let arr = new PowerArray(1, 2, 5, 10, 50);
alert(arr.isEmpty()); // false
let filteredArr = arr.filter(item => item >= 10);
alert(filteredArr); // 10, 50
alert(filteredArr.isEmpty()); // false
alert(arr.constructor === PowerArray); // true
我们甚至可以给这个类添加一个特殊的静态 getter
JAVASCRIPTSymbol.species,它会返回 JavaScript 在内部用来在 map 和 filter 等方法中创建新实体的 constructor。
如果我们希望像 map 或 filter 这样的内建方法返回常规数组,我们可以在 Symbol.species 中返回 Array,就像这样:class PowerArray extends Array {
isEmpty() {
return this.length === 0;
}
// 内建方法将使用这个作为 constructor
static get [Symbol.species]() {
return Array;
}
}
let arr = new PowerArray(1, 2, 5, 10, 50);
alert(arr.isEmpty()); // false
// filter 使用 arr.constructor[Symbol.species] 作为 constructor 创建新数组
let filteredArr = arr.filter(item => item >= 10);
// filteredArr 不是 PowerArray,而是 Array
alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function
注:内建类不会继承静态方法。因为他们之间不是extends的关系;作为构造函数,它们之间只有.prototype存在继承关系([[Prototype]]);而它们本身之间不存在[[Prototype]]的关系。
类型检查
省流:
| 用于 | 返回值 | |
|---|---|---|
typeof | 原始数据类型 | string |
{}.toString | 原始数据类型,内建对象,包含 Symbol.toStringTag 属性的对象 | string |
instanceof | 对象 | true/false |
obj instanceof Class // 返回obj是否为Class或者其衍生类
执行逻辑:
- 如果这儿有静态方法
Symbol.hasInstance,那就直接调用这个方法: - 大多数 class 没有
Symbol.hasInstance。在这种情况下,标准的逻辑是:使用obj instanceOf Class检查Class.prototype是否等于obj的原型链(不停叠加.__proto__)中的原型之一。
Mixin
JS没有多继承,所以就有了mixin,它是一个无需继承方法就能被其他类使用的类。
JAVASCRIPT// mixin
let sayHiMixin = {
sayHi() {
alert(`Hello ${this.name}`);
},
sayBye() {
alert(`Bye ${this.name}`);
}
};
// 用法:
class User {
constructor(name) {
this.name = name;
}
}
// 拷贝方法
Object.assign(User.prototype, sayHiMixin);
// 现在 User 可以打招呼了
new User("Dude").sayHi(); // Hello Dude!
Error
经典try catch throw:
JAVASCRIPTtry {
//...
} catch (err) {
//...
} // finally {} optional.无论如何都会执行
error的属性:name、message、stack(当前调用栈),等等。
抛出错误使用throw,后面可以接任何东西,但是最好使用含有name和message属性的对象。
内建error对象:Error,SyntaxError,ReferenceError,TypeError 等。它们的构造器均接受(msg)的参量。
可以在catch块中加入各种instanceof来判断错误类型,甚至可以再次throw错误。
注意:即使try中有return,finally中的内容依然会执行。Async
回调
JS中的代码异步调用,所以在执行下文的操作时上文可能还并没有执行完成。
有一个很简单的解决方案(这里面用了一些DOM方法):
JAVASCRIPTfunction loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(null, script); //callback(err, script)传入你想要在加载之后执行的函数
script.onerror = () => callback(new Error(`Script load error for ${src}`));
document.head.append(script);
}
但是如果要多层嵌套的话就炸了,怎么办?答案是使用
JAVASCRIPTPromise对象。let promise = new Promise(function(resolve, reject) {
// 当 promise 被构造完成时,自动执行此函数
// 1 秒后发出工作已经被完成的信号,并带有结果 "done"
setTimeout(() => resolve("done"), 1000);
});
当 executor 获得了结果(以下两者只能最终出现一个,之后的都会被忽略):
resolve(value)—— 如果任务成功完成并带有结果value。reject(error)—— 如果出现了 error,error即为 error 对象。 内部属性(你无法直接访问,只能通过类方法):state—— 最初是"pending",然后在resolve被调用时变为"fulfilled",或者在reject被调用时变为"rejected"。result—— 最初是undefined,然后在resolve(value)被调用时变为value,或者在reject(error)被调用时变为error。 处理得到的内容:
promise.then(
function(result) { /* handle a successful result */ },
function(error) { /* handle an error */ }
); // 如果只想要result,第二个参数可以不填
// .catch(f) 与 promise.then(null, f) 一样
promise.catch(alert); // 1 秒后显示 "Error: Whoops!"
还有
promise.finally(f)的方法,它类似于.then(f, f),区别:finally处理程序(handler)没有参数。在finally中,我们不知道 promise 是否成功。这没关系,因为我们的任务通常是执行“常规”的完成程序(finalizing procedures)。finally处理程序将结果或 error “传递”给下一个合适的处理程序。 例如,在这结果被从finally传递给了then:
new Promise((resolve, reject) => {
setTimeout(() => resolve("value"), 2000)
})
.finally(() => alert("Promise ready")) // 先触发
.then(result => alert(result)); // <-- .then 显示 "value"
总结:
finally处理程序没有得到前一个处理程序的结果(它没有参数)。而这个结果被传递给了下一个合适的处理程序。- 如果
finally处理程序返回了一些内容,那么这些内容会被忽略。 - 当
finally抛出 error 时,执行将转到最近的 error 的处理程序。 多个Promise可以形成链式结构。你可以加上无数个.then,每一个接受上一个的返回值(或者Promise,如果这样其它的程序会等待它settled——即fulfilled或rejected——之后调用)。
JAVASCRIPT这和多个分立的promise.then(result)结构不同,后者产生的是树形结构。
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result); // 1
return new Promise((resolve, reject) => { // (*)
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) { // (**)
alert(result); // 2
return new Promise((resolve, reject) => {
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) {
alert(result); // 4
});
如果
.then(或 catch/finally 都可以)处理程序返回一个 promise,那么链的其余部分将会等待,直到它状态变为 settled。当它被 settled 后,其 result(或 error)将被进一步传递下去。相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...