专栏文章

速通JS Day1-7

科技·工程参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@mipgf7kd
此快照首次捕获于
2025/12/03 11:35
3 个月前
此快照最后确认于
2025/12/03 11:35
3 个月前
查看原文

Learn From Scratch

开头使用"use strict"来使用ECMA5及以后的标准。 最好在每一个语句之后加上分号;。 使用let, const而非var!!!!!(let有高贵的块级作用域,没有声明提升 hoisting) HTML里面可以引用外部脚本文件(下载到缓存),但是标签需要闭合:
HTML
<script src="relative_or_absolute_path/script.js"></script>
数据类型:Number(±Infinity NaN)、BigInt±(253-1)、String(格式字符串的使用反引号${})、Boolean、null(无、空、值未知)、undefined(未被赋值)、Object、Symbol。可以强制类型转换。 数字转换:
变成……
undefinedNaN
null0
true / false1 / 0
string“按原样读取”字符串,两端的空白字符(空格、换行符 \n、制表符 \t 等)会被忽略。空字符串变成 0。转换出错则输出 NaN
使用 typeof 来查询数据类型。注意的Feature:null->class function(本应为class)->function
交互方法:alert(msg), prompt(msg,[initial_input]), confirm(question)
只要有一个字符串,+变为concatenate,单元的+可以等同为转换为数字。优先级一元>二元(幂>乘除>加减)
字符串比较为Unicode字典序,数字比较显然,不同类型转化为Number再比较。严格相等/不等不转换类型。(严格运算为三个字符,不严格运算为两字符)
除了 nullundefined
两者之间:null==undefined, null!==undefined(官方CP,null 只与 undefined 互等);在与其他东西比较的时候都会转化为NumberNaN与别的数字比较总会是False
布尔转换(除了"\t0\n"之类的都相当于->Number->Boolean):
  • 数字 0、空字符串 ""nullundefinedNaN 都会被转换成 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)。循环可以加上tag tag:for(;;) 做到多层的break/continue:break/continue tag;。 存在switch-case-[break]-default语句(case可以为变量),比较时执行===。 函数是第一公民。函数表达式可以认为是λ函数(但是可以有自己的名称,可以副职,匿名赋值时末位加分号,且运行时即刻创建/销毁)。函数可以作为参数。 函数在严格模式下具有块作用域。预处理时便直接声明(不用考虑先后顺序)。 好吧对不起箭头函数才是λ函数。
JAVASCRIPT
let func = (a, b) => a + b; //剪头后也可以插入代码块

Object

Basic

JS定义对象的语法非常反人类。(定义类的语法更是一坨,直到ES6才有) 类似Python,JS的key(必须是字符串,但是无空格的字符串使用的是自然写法)-value可以随时创建(对于实例直接赋值)与删除(delete Instance.attr),你甚至可以使用多个单词的key(要求使用""包括,引用时使用Object["String"]——这个用法对于单个单词的key也适用,下文中user["name"]也是合法的)。 对象的属性名甚至可以使用保留字,或者是数字等等(在找key的情况下,所有[]里面的东西都会被自动转化为字符串,但你依然可以使用纯数字+方括号来索引)。
JAVASCRIPT
let key = "double words"
let attr = prompt("Please input a string: ")
let user = {
  name: "John",
  age: 30, //建议最后的属性要带上逗号
  [attr]: undefined, //这种带中括号的即 计算属性,可以直接算出来中括号中的值作为key
}
user[key] = true;
定义的时候可以使用属性值简写,有点像构造函数:
JAVASCRIPT
function makeUser(name, age) {
  return {
    name, // 与 name: name 相同
    age,  // 与 age: age 相同
    // ...
  };
}
但是不一定要使用函数的形式,在普通的let语句中也可以这样用(别害怕,找不到的时候会给你undefined的)。判断是否有某个属性的方法为key in Object。 对于对象,可以使用for.in来遍历key:
JS
for(let key in Obj) {
	alert(`key-value: ${key}-${Obj[key]}`);
}
排列顺序:整数属性会排序,剩下的按照定义顺序。
整数属性:先强制转换为Number再强制转换为String与原来相同的key("49"是,"+49","1.2"均不是)。
为了防止排序,你的key可以写成"+Integer"的形式。这样就会被认为这是一个字符串。
依然类似Python,任何mutable的东西(比如对象)都是引用赋值,共享地址。 因此,比较对象时,当且仅当地址相等,==, ===的结果才是True(类似is) 如果你需要拷贝对象:一、使用for.in挨个赋值;二、Object.assign(target, src1, src2, ...)。 如果你想要deepclone:你得调库,或者递推地拷贝。 所有不可达的对象都会被自动垃圾回收。 方法定义:(在类继承时会有区别)
JAVASCRIPT
user = {
  sayHi: function() {
    alert("Hello");
  }
};

// 方法简写看起来更好,对吧?
let user = {
  sayHi() { // 与 "sayHi: function(){...}" 一样
    alert("Hello");
  }
};
可选链:value?.prop返回 value既不是null也不是undefinedvalue.propundefined。 而且还有 ?.(),?.[] 的变体。总结就是:只要左边是undefined就会直接短路。

典中典之this

JS中任何东西都会有this。对于类方法,this就是当前对象(当你要调用属性的时候必须使用this)。 所有的this都是在运行的时候即时算出来的。 没有this绑定。箭头函数没有this
JAVASCRIPT
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 会报错
如果想要链式调用(调用方法后产生一个可以使用的对象),你可以参考以下例子:
JS
let 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;
	}
}

幽默构造函数

构造函数并非定义在类中(或者是类方法)!所有构造函数都会是普通函数。
JAVASCRIPT
function 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
JAVASCRIPT
function User() {
  alert(new.target);
}

// 不带 "new":
User(); // undefined

// 带 "new":
new User(); // function User { ... }(返回该函数)
// 顺带一提,new Obj(...)[...]的语法是合法的
这样可以提供一种不需要new的神奇方法(库中常用,但是要慎用):
JAVASCRIPT
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

只有这个数据类型与String能够作为对象的键值。 并且它不会被自动转换成字符串,而是报错(只能.toString()或者.description)。 它跟 Ruby 的 Symbol 也是不一样的。尽管很像。
JAVASCRIPT
let id1 = Symbol("id");
let id2 = Symbol("id");//key可以留空(无参数)

alert(id1 == id2); // false,这是对象
看起来没什么用,但是如果你使用第三方的对象的时候,使用Symbol可以防止不同组建之间对对象的修改不会互相影响。也即,Symbol属性为“隐藏”属性。 如果在定义对象时使用Symbol,key值必须使用方括号。并且for.in不会遍历到SymbolObject.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 尝试查找并调用三个对象方法:
  1. 调用 obj[Symbol.toPrimitive](hint) —— 带有 symbol 键 Symbol.toPrimitive(系统 symbol)的方法,如果这个方法存在的话,
  2. 否则,如果 hint 是 "string" —— 尝试调用 obj.toString()(优先) 或 obj.valueOf(),无论哪个存在。
  3. 否则,如果 hint 是 "number""default" —— 尝试调用 obj.valueOf()(优先) 或 obj.toString(),无论哪个存在。
JAVASCRIPT
obj[Symbol.toPrimitive] = function(hint) {
  // 这里是将此对象转换为原始值的代码
  // 它必须返回一个原始值
  // hint = "string"、"number" 或 "default" 中的一个
}
如果 toStringvalueOf 返回了一个对象,那么返回值会被忽略(和这里没有方法的时候相同)。 默认情况下,普通对象具有 toStringvalueOf 方法:
  • toString 方法返回一个字符串 "[object Object]"
  • valueOf 方法返回对象自身。

Attributes

对象属性除了value,还有三个标志:
  • writable — 如果为 true,则值可以被修改,否则它是只可读的。
  • enumerable — 如果为 true,则会被在循环中列出(for.in),否则不会被列出。
  • configurable — 如果为 true,则此属性可以被删除,这些特性(列表中的三者)也可以被修改,否则不可以。
JAVASCRIPT
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创建参数,否则会更新其值。 一般默认的内置类型转换等方法的enumerablefalse(无法使用for.inObject.keys()),但是如果显性重新定义后则enumerable变为True。 一次修改多个:Object.defineProperties(obj, { prop1: descriptor1, prop2: descriptor2 , ...}); 一次获得所有属性描述符:Object.getOwnPropertyDescriptor(obj)

Accessor

有点Python的@property内味了。
JAVASCRIPT
let obj = {
  get propName() {/* 当读取 obj.propName 时,getter 起作用*/},
  set propName(value) {/* 当执行 obj.propName = value 操作时,setter 起作用 */}
};
相应地,访问器属性描述符没有writable,取而代之的是无参数函数get()和单参数函数set(value)。一个属性也要么是数据类型(有value)要么是访问器类型(get/set)。
JAVASCRIPT
Object.defineProperty(user, 'fullName', {
  get() {
    return `${this.name} ${this.surname}`;
  },

  set(value) {
    [this.name, this.surname] = value.split(" ");
  }
});
命名约定:以下划线开头的属性(例如_name)是内部属性,不应从外部访问,你可以使用get/set对其访问/赋值来操作。 代码更新时兼容旧属性的例子:(参见
JAVASCRIPT
function 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()。 同理,其他对象(ArrayDateFunction等等)都在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!) 于是我们得到了真正的对象克隆的完全体:
JAVASCRIPT
let clone = Object.create(
  Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj)
);

Types

超级比较相等的方法:SameValueZero(x,y)参见ecma

Primitive

原始类型也有方法(不包括nullundefined),但是为了尽可能保证原始的数据类型尽可能轻量,实现方法为“对象包装器”。注意对象包装器不能写入数据。
  • 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,它类似于 === 一样对值进行比较,但它对于两种边缘情况更可靠:
  1. 它适用于 NaNObject.is(NaN, NaN) === true,这是件好事。
  2. 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])startend(不含 end
substring(start[, end])startend(不含 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,如果找到则返回索引,否则返回 -1arr.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)
几乎所有调用函数的数组方法 —— 比如 findfiltermap,除了 sort 是一个特例,都接受一个可选的附加参数 thisArgthisArg 参数的值在 func 中变为 this

Iterable

比方说Python中的range就是可迭代的(以及yield等等)。只有可迭代对象才能用for.of
JAVASCRIPT
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等价于以下显式调用迭代器的过程:
JAVASCRIPT
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):
JAVASCRIPT
let str = '𝒳😂';

// 将 str 拆分为字符数组
let chars = Array.from(str);

alert(chars[0]); // 𝒳
alert(chars[1]); // 😂
alert(chars.length); // 2

JS中还有 MapSet(我嘞个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){...})ArrayforEach相似。 如果我们想从一个已有的普通对象(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中更加神人的WeakMapWeakSetWeakMap的key必须是对象而不能是原始值。并且,其中的作为Key的对象必须存在其他引用,否则会被垃圾回收!幽默弱引用。
JAVASCRIPT
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

喜欢yield吗?那么你就要使用function*
JAVASCRIPT
function* generateSequence() {
  yield 1;
  yield 2;
  return 3;
}

// "generator function" 创建了一个 "generator object"
let generator = generateSequence();
alert(generator); // [object Generator]
赋值的时候,迭代器里的代码还没有真正运行。 当然每个迭代器都是可以调用.next()的,与前文相仿,里面会有valuedone两个属性。 使用for.of循环时不会遍历最后的return值,你可以把它改成yield。 我们可以把上文中某个例子改成更加紧凑的形式:
JAVASCRIPT
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
套娃:
JAVASCRIPT
function* 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不是一个纯输出的结构,你当然可以给它整上输入(generator.next(input))。
JAVASCRIPT
function* gen() {
  // 向外部代码传递一个问题并等待答案
  let result = yield "2 + 2 = ?"; // (*)

  alert(result);
}

let generator = gen();

let question = generator.next().value; // <-- yield 返回的 value

generator.next(4); // --> 将结果传递到 generator 中
你甚至还能给generator报错(generator.throw(err)):
JAVASCRIPT
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:这还用学?
JAVASCRIPT
let [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。(注意,调用函数的时候参数不能留空,而数组可以。)
JAVASCRIPT
let 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:JSON.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表示格式化所需要的空格数量。
JAVASCRIPT
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中原则上你可以传入任意个数的参数,当形参对于实参时多出部分变成undefined,反之则会忽略多余的实参。 如果想要收集多余参数,可以使用...args(可以使用其他名称)。一定要把它放在最后一位。args会成为一个数组。 有一个名为 arguments 的特殊类数组对象可以在函数中被访问,该对象以参数在参数列表中的索引作为键,存储所有参数。箭头函数没有arguments,与this类似。(它访问的arguments相当于外部函数的值。) 如果想要将数组/可迭代对象(不能直接用于类数组,必须得要Array.from())进行解包操作,依然可以在参数中写成...arr(此时当然不一定放在最后)。可以发现如果将...替换为*,以上的两个操作将会变为我们在Python中熟悉的形式。 因此,我们又获得了浅拷贝的另一种形式[...arr],{...obj}。 一个高级的柯里化实现:
JAVASCRIPT
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));
      }
    }
  };

}

作用域

letconst均为块作用域,无声明提升。JS中存在闭包。每次调用函数都会产生一个全新的词法环境。这块跟Python几乎一模一样(把所谓Lexical enviroment替换为Frame就一样了)。参见这一章的内容(强烈建议把习题都做一遍)。 JS中的全局对象为globalThis。在浏览器中,它叫做window;在Node.js中它叫做global,在别的环境中可能会有别名。 全局对象的属性/方法可以直接调用,省略globalThis.;使用var声明的全局变量会变为全局对象的属性(不要这么做!)。全局函数声明也会有同样的效果(函数表达式、箭头函数都不是函数声明)。你也可以直接编辑全局变量的某些属性,来等效地让它变为全局的。

函数对象

函数的名字可以使用func.name(没有括号!)来访问,即使在创建时使用函数表达式赋值依然能够正确地识别(上下文命名)。如果上下文命名失败,name=""func.length返回形参个数(不包括可变参数)。 在函数内部也可以定义属性(请与变量区分)。你可以用它来部分地代替闭包(如果你想要某个参数能被外部代码修改那就使用函数属性,否则使用闭包)。
JAVASCRIPT
function sayHi() {
  alert("Hi");
  // 计算调用次数
  sayHi.counter++;
}
sayHi.counter = 0; // 初始值
sayHi(); // Hi
sayHi(); // Hi
alert( `Called ${sayHi.counter} times` ); // Called 2 times
对于命名的函数表达式(尽管有名字但依旧不是函数声明)有两个特性:在函数体内能够递归调用;在函数外其名称不可见。 正如“函数对象”其名,函数也可以使用new来构造:
JAVASCRIPT
let func = new Function ([arg1, arg2, ...argN], functionBody);
其中的所有参数均为字符串。
JAVASCRIPT
let sum = new Function('a', 'b', 'return a + b');
//let sum = new Function('a , b', 'return a+b');
alert( sum(1, 2) ); // 3
这种情况常见于要从服务器接受代码来动态编译函数。注意此时得到的函数的词法环境为全局变量!

装饰器种种

并没有美丽的@语法,你只能直接调用来函数装饰。当装饰到含有this的函数时,可以使用func.call(context, ...args)方法,第一个参数是为this
JAVASCRIPT
func(1, 2, 3); //this == globalThis
func.call(obj, 1, 2, 3); //this == obj

/*
function hash() {
	return [].join.call(arguments); //方法借用
}
*/
如果你不想传入参数列表而是一个类数组,那么将call变为apply即可。 如果你只想要得到一个绑定了this的函数,请使用func.bind(context)(一个函数只能被绑定一次)。bind结束后得到的是另一个对象,它不会保留原来加上的函数属性。
JAVASCRIPT
let bound = func.bind(context, [arg1], [arg2], ...);
是的,完整版的func.bind还可以绑定参数。这样比使用闭包来调用方便多了。但是如果你只想要绑定参数,你确实可以自定义一个东西:
JAVASCRIPT
function partial(func, ...argsBound) {
  return function(...args) { // (*)
    return func.call(this, ...argsBound, ...args);
  }
}

=>

箭头函数没有this,没有arguments,它全部从外界获取。箭头函数不会离开当前的上下文。 同理,箭头函数不能作为构造器,不能将其用到new后面。它也没有super

Class

基础

笑点解析:继承都讲完了才出现类。
JAVASCRIPT
class MyClass {
  // class 方法
  constructor() { ... }
  method1() { ... }
  method2() { ... }
  method3() { ... }
  ...
}
let class = new MyClass();
alert(typeof User); // function
类方法之间没有括号! 正如上文,类是一种函数(令人忍俊不禁)。也就是说,MyClass === MyClass.prototype.constructor;上面的constructor与每个methodMyClass.prototype的方法。(也就是说,每个创建的对象相当于继承了类。) 那么,为什么我不直接使用普通的构造函数呢?通过 class 创建的函数具有特殊的内部属性标记 [[IsClassConstructor]]: true。因此,它与手动创建并不完全相同。例如,与普通函数不同,必须使用 new 来调用它;类有自己的toString类方法不可枚举(不能使用for.in);等等。 当然类也有类表达式之说,类表达式也可以有自己的姓名。
JAVASCRIPT
// “命名类表达式(Named Class Expression)”
// (规范中没有这样的术语,但是它和命名函数表达式类似)
let User = class MyClass {
  sayHi() {
    alert(MyClass); // MyClass 这个名字仅在类内部可见
  }
};

new User().sayHi(); // 正常运行,显示 MyClass 中定义的内容

alert(MyClass); // error,MyClass 在外部不可见
同理,类也可以使用getter.setter等等,或者使用计算属性(中括号内部的东西)。 所谓类字段,就是在定义类的时候加入属性(这个在C/C++看起来这么显而易见的东西居然在最近才放入标准,可能需要polyfill)。
JAVASCRIPT
class User {
  name = "John"; //孩子们很多旧版本都不支持我
  //你甚至可以使用 name = prompt("Name, please?", "John") 这种语句。

  sayHi() {
    alert(`Hello, ${this.name}!`);
  }
}

new User().sayHi(); // Hello, John!
所有类字段会挂在实例对象而非原型上。 回忆:在某些时候使用类方法做其它函数的参数会出现this丢失的问题:
JAVASCRIPT
class Button {
  constructor(value) {
    this.value = value;
  }

  click() {
    alert(this.value);
  }
}

let button = new Button("hello");

setTimeout(button.click, 1000); // undefined
这时候有三种方法:
  1. 传递一个包装函数,例如 setTimeout(() => button.click(), 1000)
  2. 将方法绑定到对象(func.bind),例如在 constructor 中。
  3. 在类中写下click = () => { alert(this.value) }。这对于事件监听非常有用。

继承

JAVASCRIPT
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.`);
  }
}

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!
此时 Rabbit.prototype.[[prototype]] === Animal.prototype。 注意,extends之后可以写任意表达式:
JAVASCRIPT
function f(phrase) {
  return class {
    sayHi() { alert(phrase); }
  };
}

class User extends f("Hello") {}

new User().sayHi(); // Hello
找爹:使用super关键字来更加灵活地重写部分属性。(箭头函数依然没有super,只会从外部找。)
  • 执行 super.method(...) 来调用一个父类方法。
  • 执行 super(...) 来调用一个父类 constructor(只能在我们的 constructor 中)。
JAVASCRIPT
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!
哦天呐,我们还没有重写构造器呢!默认情况下如果没写构造器那就和super产生一样的行为。让我们来试试:
JAVASCRIPT
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的一切

你可能觉得调用super的方法只需要将其替换为this.__proto__就可以了:
JAVASCRIPT
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添加了特殊内部属性[[HomeObject]]。一个类/对象的方法的[[HomeObject]]就是该类/对象。于是调用super的时候就会从它的[[HomeObject]]中寻找。 很遗憾,[[HomeObject]]是永久绑定的,无法被更改。
JAVASCRIPT
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++。static标签中调用的this就是类构造器。 static方法都是挂载在Class而非Class.Prototype。 通常,静态方法用于实现属于整个类,但不属于该类任何特定对象的函数。
JAVASCRIPT
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
另一个例子:
JAVASCRIPT
class 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.#aobj["#a"]等方式来访问。大部分情况下你只能使用polyfills实现。

内建的许多类(Array, Map等)都是可以扩展的。
JAVASCRIPT
// 给 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 Symbol.species,它会返回 JavaScript 在内部用来在 mapfilter 等方法中创建新实体的 constructor。 如果我们希望像 mapfilter 这样的内建方法返回常规数组,我们可以在 Symbol.species 中返回 Array,就像这样:
JAVASCRIPT
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
JAVASCRIPT
obj instanceof Class // 返回obj是否为Class或者其衍生类
执行逻辑:
  1. 如果这儿有静态方法 Symbol.hasInstance,那就直接调用这个方法:
  2. 大多数 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:
JAVASCRIPT
try {
//...
} catch (err) {
//...
} // finally {} optional.无论如何都会执行
error的属性:namemessagestack(当前调用栈),等等。 抛出错误使用throw,后面可以接任何东西,但是最好使用含有namemessage属性的对象。 内建error对象:ErrorSyntaxErrorReferenceErrorTypeError 等。它们的构造器均接受(msg)的参量。 可以在catch块中加入各种instanceof来判断错误类型,甚至可以再次throw错误。 注意:即使try中有returnfinally中的内容依然会执行。

Async

回调

JS中的代码异步调用,所以在执行下文的操作时上文可能还并没有执行完成。 有一个很简单的解决方案(这里面用了一些DOM方法):
JAVASCRIPT
function 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);
}
但是如果要多层嵌套的话就炸了,怎么办?答案是使用Promise对象。
JAVASCRIPT
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。 处理得到的内容:
JAVASCRIPT
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
JAVASCRIPT
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——即fulfilledrejected——之后调用)。
这和多个分立的promise.then(result)结构不同,后者产生的是树形结构。
JAVASCRIPT
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 条评论,欢迎与作者交流。

正在加载评论...