$ npm init -y
#查看当前镜像源
$ npm get registry
#切换淘宝镜像,直接更换地址法
$ npm config set registry http://registry.npm.taobao.org/
格式:
{
"presets": [],
"plugins": []
}
presets字段设定转码规则,官方提供以下的规则集,按需安装
# 最新转码规则
$ npm install --save-dev babel-preset-latest
# react 转码规则
$ npm install --save-dev babel-preset-react
# 不同阶段语法提案的转码规则(共有4个阶段),选装一个
$ npm install --save-dev babel-preset-stage-0
$ npm install --save-dev babel-preset-stage-1
$ npm install --save-dev babel-preset-stage-2
$ npm install --save-dev babel-preset-stage-3
配置规则:
{
"presets": [
"latest",
"react",
"stage-2"
],
"plugins": []
}
注:后续babel相关工具和模块的使用,.babelrc配置好是前提
安装命令:
#全局安装
$ npm install --global babel-cli
# 安装在项目中
$ npm install --save-dev babel-cli
基本用法:
#转码结果输出到标准输出
$ babel example.js
#转码结果写入一个文件
# --out-file 或 -o 参数指定输出文件
$ babel example.js --out-file compiled.js
# 或者
$ babel example.js -o compiled.js
#整个目录转码
# --out-dir 或 -d 参数指定输出目录
$ babel src --out-dir lib
#或者
$ babel src -d lib
# -s 参数生成source map文件
$ babel src -d lib -s
安装在项目中是改写package.json
{
"devDependencies": {
"babel-cli": "^6.0.0"
},
"scripts": {
"build": "babel src -d lib"
},
}
转码时执行
$ npm run build
{
"scripts": {
"script-name": "babel-node es6.js"
}
}
"使用babel-node替代node,这样script.js本身就不用做任何转码处理" ——
下载:
$ npm install --save-dev babel-register
使用:
require("babel-register");
require("./index.js");
安装命令:
$ npm install babel-core --save
使用:
var babel = require('babel-core');
// 字符串转码
babel.transform('code();', options); // => { code, map, ast }
// 文件转码(异步)
babel.transformFile('filename.js', options, function(err, result) { result; // => { code, map, ast } });
// 文件转码(同步)
babel.transformFileSync('filename.js', options); // => { code, map, ast }
// Babel AST转码
babel.transformFromAst(ast, code, options); // => { code, map, ast }
举例:
var es6Code = 'let x = n => n + 1';
var es5Code = require('babel-core')
.transform(es6Code, {
presets: ['latest']
})
.code;
// '"use strict";\n\nvar x = function x(n) {\n return n + 1;\n};'
安装:
$ npm install --save babel-polyfill
使用:
import 'babel-polyfill';
// 或者
require('babel-polyfill');
注:Babel 默认不转码的 API 非常多,详细清单可以查看babel-plugin-transform-runtime模块的definitions.js文件。
Traceur 允许将 ES6 代码直接插入网页
使用:
<script src="https://google.github.io/traceur-compiler/bin/traceur.js"></script>
<script src="https://google.github.io/traceur-compiler/bin/BrowserSystem.js"></script>
<script src="https://google.github.io/traceur-compiler/src/bootstrap.js"></script>
<script type="module">
import './Greeter.js';
</script>
var tmp=1;
if (true) {
// TDZ开始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; // TDZ结束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
函数声明与块级作用域
ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。但浏览器为兼容旧代码,不会报错
ES6 规定:(仅在浏览器的ES6环境中有效,其他环境不用遵守,即将块级作用域的函数声明当作let处理)
a. 块级作用域中,函数声明语句的行为类似 let,在块级作用域之外不可引用
b. 允许在块级作用域内声明函数(包含在大括号 {} 中才成立)
c. 函数声明会提升到所在作用域的头部,,类似 var
考虑到环境导致的行为差异太大,当需要在块级作用域中声明函数时,应采用函数表达式的形式,而不是函数声明语句
// 函数声明语句
{
let a = 'secret';
function f() {
return a;
}
}
// 函数表达式
{
let a = 'secret';
let f = function () {
return a;
};
}
const 本质
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合型数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const只能保证这个指针是固定的。
当需要将对象及其属性都变为常量(不可修改),应使用 Object.freeze 方法
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};
ES5的顶层对象在各种实现里面不统一:
顶层对象 | window | self | global |
---|---|---|---|
浏览器 | √ | √ | X |
Node | X | X | √ |
web Worker | X | √ | X |
为了取到顶层对象,现在一般使用 this 变量,但有局限性
以下两种方式勉强可以使用获取顶层对象:
// 方法一
(typeof window !== 'undefined'
? window : (typeof process === 'object' &&
typeof require === 'function' &&
typeof global === 'object')
? global : this
);
// 方法二
var getGlobal = function () {
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
};
垫片库 system.global 模拟了一个提案,可以在所有环境拿到 global。
// CommonJS 的写法
var global = require('system.global')();
// ES6 模块的写法
import getGlobal from 'system.global';
const global = getGlobal();
//注:
//p:匹配模式,
//name:真正赋值的变量
let { p : name}=obj;
const node = {
loc: {
start: {
line: 1,
column: 5 } } };
let { loc, loc: { start }, loc: { start: { line }} } = node; line // 1 loc // Object {start: Object} start // Object {line: 1, column: 5}
对象的解构赋值可以设置默认值,但生效条件是当对象的属性值严格等于 undefined
var {x = 3} = {x: undefined}; x // 3
var {x = 3} = {x: null}; x // null
解构赋值的规则是,只要等号右边的值不是对象或者数组,就先将其转为对象。由于 undefined 和 null 无法转为对象,所以对它们解构赋值会报错
let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError
变量声明语句不得使用圆括号——会引发歧义导致报错
赋值语句的非模式部分可以使用括号
JavaScript 内部,字符以 UTF-16 的格式储存。\u0000~\uFFFF 之间的字符只表示 2 个字节的字符,超出 0xFFFF 的字符需要用 4 个字节表示
codePointAt 方法是测试一个字符由 2 个字节还是由 4 个字节组成的简单方法。其返回的是十进制码点,如需十六进制的值可用 toString 方法转换
let s = 'a';
//for...of 可以正确识别 32 位的 UTF-16 字符,即识别大于0xFFFF的码点
for (let ch of s) {
console.log(ch.codePointAt(0).toString(16));
}
// 20bb7 // 61
function is32Bit(c) {
return c.codePointAt(0) > 0xFFFF;
}
is32Bit("") // true
is32Bit("a") // false
与 codePointAt 相反的操作是:将码点返回对应的字符,可用 String.fromCodePoin 方法
注:fromCodePoint方法定义在String对象上,而codePointAt方法定义在字符串的实例对象上。
let s = 'Hello world!';
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) //
true s.includes('Hello', 6) // false
//参数表示重复n次
'x'.repeat(3) // "xxx"
//第一个参数表示总字符串长度
//第二个可选参数表示需要补全的字符,无则补空
'x'.padStart(5, 'ab') // 'ababx'
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padStart(4) // ' x'
'123456'.padStart(10, '0') // "0000123456"
字符串对象的以下4个方法,在语言内部全部调用RegExp的实例方法,从而做到所有与正则相关的方法,全都定义在RegExp对象上。
String.prototype.match 调用 RegExp.prototype[Symbol.match]
String.prototype.replace 调用 RegExp.prototype[Symbol.replace]
String.prototype.search 调用 RegExp.prototype[Symbol.search]
String.prototype.split 调用 RegExp.prototype[Symbol.split]
ES6添加了 u 修饰符,含义为“Unicode 模式”,能正确处理大于 \uFFFF 的 Unicode 字符,即4字节的 UTF-16 编码
当使用 u 字符后,以下正则修饰符的行为就会改变
var s = '';
/^.$/.test(s) // false
/^.$/u.test(s) // true
注:若要点字符匹配任意字符,可加上 “s” 修饰符
/\u{61}/.test('a') // false
/\u{61}/u.test('a') // true
/\u{20BB7}/u.test('') // true
”先行断言“指的是,x只有在y前面才匹配,必须写成 /x(?=y)/
”先行否定断言“指的是,x只有不在y前面才匹配,必须写成 /x(?!y)/
“后行断言”,x只有在y后面才匹配,必须写成 /(?<=y)x/
”后行否定断言“,x只有不在y后面才匹配,必须写成 /(?
返回结果均不包含括号里的内容。采用先从左到右扫描,发现匹 配以后再回过头,即“先右后左”的执行顺序
/\d+(?=%)/.exec('100% of US presidents have been male') // ["100"]
/\d+(?!%)/.exec('that’s all 44 of them') // ["44"]
/(?<=\$)\d+/.exec('Benjamin Franklin is on the $100 bill') // ["100"]
/(?<!\$)\d+/.exec('it’s is worth about €90') // ["90"]
后行断言的组匹配
//由于执行顺序是从右到左,第二个括号是贪婪模式,第一个括号只能捕获一个字符,所以结果是1和053
/(?<=(\d+)(\d+))$/.exec('1053') // ["", "1", "053"]
//第一个括号是贪婪模式,第二个括号只能捕获一个字符,所以结果是105和3
/^(\d+)(\d+)$/.exec('1053') // ["1053", "105", "3"]
6.2 Number.isFinite(), Number.isNaN()
检测数值是否有限和是否非数字
6.3 Number.parseInt(), Number.parseFloat()
6.4 Number.isInteger()
判断值是否为整数
6.5 Number.EPSILON
是JS能够表示的最小精度,其目的在于为浮点数计算,设置一个误差范围
function withinErrorMargin (left, right) {
return Math.abs(left - right) < Number.EPSILON * Math.pow(2, 2);
}
0.1 + 0.2 === 0.3 // false
withinErrorMargin(0.1 + 0.2, 0.3) // true
1.1 + 1.3 === 2.4 // false
withinErrorMargin(1.1 + 1.3, 2.4) // true
6.7 Math 对象的扩展
Math.trunc = Math.trunc || function(x) {
return x < 0 ? Math.ceil(x) : Math.floor(x);
};
Math.sign() 判断一个数到底是正数、负数、还是零
返回值:
参数为正数,返回+1;
参数为负数,返回-1;
参数为 0,返回0;
参数为-0,返回-0;
其他值,返回NaN。
手动实现:
Math.sign = Math.sign || function(x) {
x = +x; // convert to a number
if (x === 0 || isNaN(x)) {
return x;
}
return x > 0 ? 1 : -1;
};
默认参数
一般至于尾部,传参时可省。置于非尾部时,不可省,可显式传入"undefined",触发默认值(“null” 无法触发默认值)
设置默认属性后,函数的 length 属性不会将设有默认值的参数及其后的参数计入总数
rest 参数
形式为:…变量名,是一个数组(arguments对象是类数组)rest参数只能置于末尾
箭头函数
箭头函数使用注意点:
a. 函数体内的 this 对象,就是【定义】时所在的对象,而不是使用时所在的对象
b. 不可当作构造函数,即不可以使用 new 命令,否则抛出错误
c. 不可使用 arguments 对象,该对象在函数体内不存在。如需使用,可用 rest 参数代替
d. 不可使用 yield 命令, 即不能用作 Generator 函数
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
var id = 21;
foo.call({ id: 42 }); // id: 42
//foo函数生成时,setTimeout 的 this 就被指定了。setTimeout 等到执行时 this 不会像普通函数一样指向 window
foo::bar;
// 等同于
bar.bind(foo);
foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);
//尾调用优化例子:
function f() { let m = 1; let n = 2; return g(m + n); } f();
// 等同于
function f() { return g(3); } f();
// 等同于
g(3);
//尾调用不能优化例子:
function addOne(a){
var one = 1;
function inner(b){
return b + one;
}
return inner(a);
}
//调用inner时只传了b,内部需要计算的one,还需向父级获取,所以不能删除父级的调用帧,无法进行优化
function Fibonacci (n) {
if ( n <= 1 ) {
return 1
};
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
Fibonacci(10) // 89
Fibonacci(100) // 堆栈溢出
Fibonacci(500) // 堆栈溢出
//尾递归优化:
function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
if( n <= 1 ) {
return ac2
};
return Fibonacci2 (n - 1, ac2, ac1 + ac2);
}
Fibonacci2(100) // 573147844013817200000
Fibonacci2(1000) // 7.0330367711422765e+208
Fibonacci2(10000) // Infinity
尾调用优化仅在严格模式下开启,因为正常模式下函数内部有两个变量可以跟踪函数的调用栈,严格模式下会改写调用栈,这两个变量会失真
func.arguments:返回调用时函数的参数。
func.caller:返回调用当前函数的那个函数
正常模式下,需要手动改写尾递归为“循环”,这样减少调用栈就不会造成溢出了
8.1 扩展运算符
扩展运算符用于数组赋值时只能放在参数的最后一位
//当一个函数需要接受单个参数,但调用该方法时传入的参数是数组时,可以用扩展运算符快捷传入
//普通函数
function f(x, y, z) { // ... }
var args = [0, 1, 2];
// ES5 的写法
f.apply(null, args);
// ES6的写法
f(...args);
//数组插入数据
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
//ES5
Array.prototype.push.apply(arr1, arr2);
//ES6
arr1.push(...arr2);
//复制数组
//数组时复合数据类型,简单复制时只能复制指针指向,原数组和新数组指向的是同一份数据
const a1 = [1, 2];
//以下是拷贝新数组的方式:
//ES5:变通方式
const a2 = a1.concat();
a2[0] = 2; // a1 = [1, 2]
//ES6
const a2 = [...a1]; // 或者
const [...a2] = a1;
扩展运算符能将字符串转为数组,且能正确识别 4 字节的 Unicode 字符
凡有 Iterator 接口的对象都能用扩展运算符转为数组
8.2 Array.from()
能将类似数组的对象(array-like object,必有 length 属性)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)转为真正的数组,类数组对象常见的有函数内部的 arguments 对象,DOM操作返回的 NodeList 集合
注:1. 扩展运算符底层调用的是 Symbol.iterator ,所以转换的对象必须要部署这个接口
2. Array.from 能转换类数组对象,只要转换对象里包含 length 属性就能被转换真正的数组
8.3 Array.of
Array.of 用于将一组值转换为数组。基本上可以用来替代Array()或new Array(),并且不存在由于参数不同而导致的重载。
方法模拟:
function ArrayOf(){
return [].slice.call(arguments);
}
8.4 copyWithin()
// 将3号位复制到0号位 ,4:结束位置
[1, 2, 3, 4, 5].copyWithin(0, 3, 4)
// [4, 2, 3, 4, 5]
// 对于没有部署 TypedArray 的 copyWithin 方法的平台 需要采用下面的写法
[].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4);
// Int32Array [4, 2, 3, 4, 5]
8.5 find() 和 findIndex()
此方法用于找出第一个符合条件的数组成员或成员所在的位置。
其参数是一个回调函数,所有数组成员依次执行该回调函数,找不到符合条件的成员,返回 undefined或 “-1”
其参数有3个,依次为当前值、当前位置、原数组
这两个方法可以弥补 indexof 方法的不足,能找出为 “NaN” 的成员( indexof 方法内部进行严格相等运算符“===”进行运算,导致对 NaN 误判)
[1, 5, 10, 15].findIndex(function(value, index, arr) {
return value > 9;
}) // 2
[NaN].indexOf(NaN) // -1
[NaN].findIndex(y => Object.is(NaN, y)) // 0
8.6 fill()
8.7 entries(),keys() 和 values()
8.8 includes()
Array.prototype.includes 方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似
Map 和 Set 数据结构有一个has方法,需要注意与includes区分
—— Map 结构的has方法,是用来查找键名
—— Set 结构的has方法,是用来查找值
8.9 数组的空位
空位不是 undefined ,空位表示没有任何值
Array(3) // [, , ,] 数组空位
0 in [undefined, undefined, undefined] // true
0 in [, , ,] // false
ES5 对空位的处理:
ES6明确规定将空位转为 undefined
9.4 Object.is()
ES5比较两个值是否相等都有缺点:
==:会动态转换数据类型
===:NaN不等于自身,+0 = -0
ES6部署的新方法 Object.is() 行为与严格比较运算符一致,但修复了上述NaN和正负零的问题
9.5 Object.assign()
此方法用于对象的合并,将源对象的所有可枚举属性(及自带属性(非继承属性))复制到目标对象
格式形如:Object.assign(target, source1, source2);
如果目标参数不是对象,该方法会默认将其转换为对象再合并(由于 undefined 和 null 无法转为对象,所以会报错)
源参数为 undefined 或 null 、布尔值、Number 时,会直接忽略
注意点:
常见应用:
为属性指定默认值
const DEFAULTS = { logLevel: 0, outputFormat: 'html' };
function processContent(options) {
options = Object.assign({}, DEFAULTS, options); //DEFAULTS, options最好都是简单类型,不要指向另一个对象(浅拷贝)
console.log(options); // ...
}
9.6 可枚举和遍历
目前,有四个操作会忽略enumerable为false的属性。
ES6 一共有 5 种方法遍历对象的属性
以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。
9.7. Object.getOwnPropertyDescriptors()
9.8. __proto__属性,Object.setPrototypeOf(),Object.getPrototypeOf()
9.9 super
super 关键字表示原型对象时,只能用在对象的方法中,用在其他地方会报错
const proto = { foo: 'hello' };
//只有这种对象方法的简写法能被JS确定
const obj = {
find() {
return super.foo;
}
};
Object.setPrototypeOf(obj, proto);
obj.find() // "hello"
9.10 Object.keys(),Object.values(),Object.entries()
三者分别返回对象自身(不包含继承)的、可遍历属性的键名、键值、键值对数组,作为遍历对象的补充手段,供 for…of循环使用
Object.values() :
返回键值数组是以键名排列的顺序为主,会过滤键名为 Symbol 值的属性
参数是字符串时返回各个字符组成的数组
参数非对象时,先将其转为对象。数值和布尔值的包装对象不会为实例添加非继承属性,所以会返回 空数组
9.11 对象的扩展运算符
解构赋值
解构赋值等号右边须是对象,undefined 和 null 会报错
解构赋值必须是最后一个参数
解构赋值是浅拷贝,不能复制继承自原型对象的属性
扩展运算符
扩展运算符(…)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。等同于 Object.assign ,仅拷贝对象实例的属性
要克隆完整的对象,还要拷贝对象原型上的属性:
// 写法一
const clone1 = {
__proto__: Object.getPrototypeOf(obj), //__proto__属性在非浏览器环境不一定部署,不推荐使用
...obj
};
// 写法二
const clone2 = Object.assign(
Object.create(Object.getPrototypeOf(obj)),
obj
);
// 写法三
const clone3 = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)
扩展运算符 放在自定义属性后面,扩展运算符里的同名属性会被覆盖,放在自定义属性前面,常用来设置新对象的默认属性值
9.12 Null 传导运算符
层层判断对象是否存在,写法:?.
//读取message.body.user.firstName
const firstName = (message
&& message.body
&& message.body.user
&& message.body.user.firstName) || 'default';
//新提案写法
const firstName = message?.body?.user?.firstName || 'default';
生成的 Symbol 是一个原始类型的值,不是对象,不能使用 new 命令。也就是说,由于 Symbol 值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型
10.2 作为属性名
设置和获取时都须用 “[ ]” 包裹,不能使用 “.”运算符
Symbol 值作为属性名时,该属性还是公开属性,不是私有属性
10.4 属性名的遍历
Symbol 作为属性名,该属性不会出现在for…in、for…of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、 JSON.stringify() 返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有 Symbol 属性名。 (Reflect.ownKeys(obj) 也可)
注:
Object.defineProperty(obj, prop, descriptor)
参数说明:要定义属性的对象、要定义或修改的属性的名称或 Symbol 、要定义或修改的属性描述符
是定义 key 为 Symbol 属性的方法之一
10.5 Symbol.for()
Symbol.for() 产生的 symbol 会被放置在全局中,下次 Symbol.for() 传参时,首先查看全局下是否有同样参数的 Symbol 无则返回新 Symbol ,有则返回原 Symbol 。即Symbol.for(‘A’) 产生的是同一个数据,而 Symbol(‘A’) 产生的是不同的数据
11.1 Set
类似数组,成员的值唯一。Set 本身是一个构造函数,用来生成数据结构
//用法:
const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x)); //添加数据
//或 s = new Set([2, 3, 5, 4, 5, 2, 2]) 可接受具有 'iterable' 接口的数据结构作为参数,用来初始化
[...s];//[2, 3, 5, 4]
s.size //4
Set 进行值判断的算法同 精准相等运算符(===),但该运算 NaN 不等于自身,Set 结构中的 NaN 等于自身
Set实例:
属性
Set.prototype.constructor:构造函数,默认就是Set函数。
Set.prototype.size:返回Set实例的成员总数。
方法
操作方法:
add(value):添加某个值,返回 Set 结构本身
delete(value):删除某个值,返回一个布尔值,表示删除是否成功
has(value):返回一个布尔值,表示该值是否为Set的成员
clear():清除所有成员,没有返回值
Array.from 方法可以将 Set 结构转为数组。所以去除数组重复数据可以: Array.from(new Set(array))
遍历方法:
keys():返回键名的遍历器
values():返回键值的遍历器 ,可用 for…of 替代循环遍历(Set结构默认遍历器生成函数就是 values 方法)
entries():返回键值对的遍历器
forEach():使用回调函数遍历每个成员
Set 遍历顺序即插入顺序,Set 结构没有键名(或键名和键值是同一个值), keys() 和 value() 方法行为一致
Set 可以很容易地实现并集(Union)、交集(Intersect)和差集(Difference)
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]); // Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x))); // set {2, 3}
// 差集
let difference = new Set([...a].filter(x => !b.has(x))); // Set {1}
11.2 WeakSet
与 Set 的区别:
WeakSet 的成员只能是对象;
WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用。垃圾回收机制运行不可预测,运行前后 WeakSet 成员数量不定,所以 ES6 规定 WeakSet 不可遍历(WeakMap 亦同)
方法:(没有 size 、forEach 属性)
用处:可用于储存 DOM 节点,而不用担心这些节点从文档移除时,会引发内存泄漏
11.3 Map
类似对象,是 “值-值” 键值对的集合,键名不限定是字符串(Object 结构提供的是 “字符串—值” 的键值对)
使用:
const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content') //键名为对象
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
const map = new Map([ //数组作参数,每个数组成员是表示键值对的数组
['name', '张三'],
['title', 'Author']
]);
map.size // 2
map.has('name') // true
map.get('name') // "张三"
注:
Map 的键实际上是跟内存地址绑定的,只要内存地址不一样就视为两个键
当键是简单类型时,只要两个值严格相等就是一个键。如 0 严格等于 -0 ,而 true 不严格等于 ‘true’,undefined 不严格等于 null
例外:NaN 不严格等于自身,但 Map 将其视为同一个键
属性和方法:
遍历方法:keys() 、value() 、entries() (默认遍历器接口) 、forEach()
Map结构可使用扩展运算符(…)快速转换为数组结构,这样能使用到数组自带的多种方法,如map、filter
forEach() 可接受第二个参数,用来绑定 this
const reporter = {
report: function(key, value) {
console.log("Key: %s, Value: %s", key, value);
}
};
map.forEach(function(value, key, map) {
this.report(key, value); //该this指向 reporter
}, reporter);
Map 与其他数据结构互转
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap)); //Map => Obj => Json
}
let myMap = new Map()
.set('yes', true)
.set('no', false);
strMapToJson(myMap) // '{"yes":true,"no":false}'
当键名有非字符串时,可转为数组JSON
function mapToArrayJson(map) {
return JSON.stringify([...map]); //Map => array => json
}
let myMap = new Map()
.set(true, 7)
.set({foo: 3}, ['abc']);
mapToArrayJson(myMap) // '[[true,7],[{"foo":3},["abc"]]]'
JSON 转 Map
正常情况下,所有键名都是字符串
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr)); //json => obj => Map
}
jsonToStrMap('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}
特殊情况,整个JSON是个数组,且成员本身又是一个有两个成员的数组
function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr)); //
}
jsonToMap('[[true,7],[{"foo":3},["abc"]]]') // Map {true => 7, Object {foo: 3} => ['abc']}
11.4 WeakMap
只接受对象作为键名(null 除外);键名所指的对象不计入垃圾回收机制,防止内存泄露
典型的应用场景是在网页上的DOM元素上添加数据,使用WeakMap结构后,当DOM元素被清除,其所对应的WeakMap记录就会自动移除
方法:get()、set()、 has()、delete()
无遍历方法
调试:如过引用所指向的值占用特别多的内存,可以通过 Node 的 process.memoryUsage方法可以看出引用是否解除
操作关键步骤:
#--expose-gc 表示允许手动执行垃圾回收机制
$ node --expose-gc
#手动执行垃圾回收
global.gc();
#外部引用一次
let arr=new Array(5*1024*1024)
#弱引用一次
let wm = new WeapMap();
wm.set(arr,1)
#在清空arr前后(执行垃圾回收)可查看内存大小变化情况
Proxy :代理。当访问目标对象时,必须先通过 “代理层”,该代理层可以对外界的访问进行过滤和改写
ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。
var proxy = new Proxy(target, handler);
//target:要拦截的目标对象
//handler:定制拦截行为
Proxy 支持的拦截操作:
this 指向问题
目标对象的内部 this 会指向 Proxy 代理,以目标对象直接访问是不经拦截的
不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。
let obj={name:'jack',age:18}
//拦截obj对象,并修改其默认方法:
var loggedObj = new Proxy(obj, {
get(target, name) {
console.log('get', target, name);
return Reflect.get(target, name);
},
deleteProperty(target, name) {
console.log('delete' + name);
return Reflect.deleteProperty(target, name);
},
has(target, name) {
console.log('has' + name);
return Reflect.has(target, name);
}
});
//运行,使用代理对象操作属性
Reflect.get(loggedObj,'name')
//get {name: "jack", age: 18} name
//"jack"
Reflect.deleteProperty(loggedObj,'name')
//deletename
//true
Reflect 有13个静态方法,与 Proxy 对象一一对应:
Reflect.set:
如果 Proxy 对象和 Reflect 对象联合使用,前者拦截赋值操作,后者完成赋值的默认行为,而且传入了receiver,那么Reflect.set会触发 Proxy.defineProperty拦截
let p = { a: 'a' };
let handler = {
set(target, key, value, receiver) {
console.log('set');
Reflect.set(target, key, value, receiver)
},
defineProperty(target, key, attribute) {
console.log('defineProperty');
Reflect.defineProperty(target, key, attribute);
}
};
let obj = new Proxy(p, handler);
obj.a = 'A';
// set
// defineProperty
//上面代码中,Proxy.set拦截里面使用了Reflect.set,而且传入了receiver,导致触发Proxy.defineProperty拦截。
//这是因为Proxy.set的 receiver参数总是指向当前的 Proxy 实例(即上例的obj),
//而Reflect.set一旦传入receiver,就会将属性赋值到receiver上面(即obj),导致触发defineProperty拦截。
//如果Reflect.set没有传入receiver,那么就不会触发defineProperty拦截。
使用 Proxy 实现观察者模式
观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行。
const queuedObservers = new Set(); //观察者集合
const observe = fn => queuedObservers.add(fn);
const observable = obj => new Proxy(obj, {set}); //返回原始对象的 Proxy ,拦截赋值操作
function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver); //赋值操作
queuedObservers.forEach(observer => observer()); //触发每一个观察者都执行一遍
return result;
}
const person = observable({ //被观察者
name: '张三',
age: 20
});
function print() { //观察者
console.log(`${person.name}, ${person.age}`)
}
observe(print);
person.name = '李四';
// 输出 // 李四, 20
Promise 是异步编程的一种解决方案,原生提供了 Promise 对象。简单来说就是一个容器,保存着某个未来才会结束的事件的结果
特点: Promise 对象的状态:pending(进行中)、fulfill(已成功)、rejected(已失败),只有异步操作的结果可以将 pending 改为 fulfill 或者rejected,一旦改变就不再变化
如果某些事件不断地反复发生,一般来说,使用 Stream 模式是比部署 Promise 更好的选择。?
14.3 Promise.prototype.then()
then 方法包含两个参数:(resolve(),reject())当前面的promise返回resolved状态即成功状态,则执行resolved方法,当返回rejected 状态即失败状态,则执行 rejected 方法
then 里面的参数方法返回的不新的 Promise 实例,所以可以采用链式写法
14.4 Promise.prototype.catch()
14.5 Promise.all()
14.6. Promise.race()
race 方法同 all 方法,只不过是当参数中有一个实例率先改变状态,那么 p 也跟着改变状态,该实例的返回值传递给 p 的回调函数
14.7. Promise.resolve()
14.8. Promise.reject()
reject 返回一个新的 promise 实例,状态为 rejected
该方法的参数,会作为 reject 的理由,变成后续方法的参数
14.9 附加方法
done():可以捕获最后一个方法抛出的错误
Promise.prototype.done = function (onFulfilled, onRejected) {
this.then(onFulfilled, onRejected)
.catch(function (reason) {
// 抛出一个全局错误
setTimeout(() => { throw reason }, 0);
});
};
finally():接受一个普通回调函数作为参数,不管 promise 的状态是什么都会执行改回调函数
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
14.10 应用
预加载图片
const preloadImage = function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
});
};
14.11. Promise.try()
15.1 Iterator
Iterator 的作用
1. 为各种数据结构提供一个统一的渐变访问接口
2. 使得数据结构的成员能按次序排列
3. 主要供 for…of 使用
遍历过程:
遍历器本质上就是一个指针对象,第一次调用指针对象的 next 方法,指针指向数据结构的第一个成员,第二次指向第二个成员…每次调用 next 会返回一个包含 value(当前成员的值) 和 done(遍历是否结束) 两个属性的对象
…
18.1 async 与 generator 的区别
1. generator 函数必须依赖执行器执行,如 co 模块能直接将其执行或者调用 next 方法;而async 函数自带执行器,可像普通函数一样直接执行
2. async 语义更清晰,async 表示函数里有异步操作,await 表示紧跟后面的表达式需等待结果
3. async 函数的await 后面可以是Promise 对象和原始类型的值(此时相当于同步操作)
4. async 函数返回值是 Promise 对象,比 Generator 返回 Iterator 对象方便,可以用 then 方法指定接下来的操作
async 函数相当于是多个异步操作包装成 Promise 对象,await 就是内部 then 命令的语法糖
async 函数内部是继发执行,外部不受影响
19.1
类的方法都是定义在 prototype 对象上,Object.assign 方法可以一次像类添加多个方法:
Object.assign(Point.prototype, {
toString(){},
toValue(){}
});
与ES5相比,类内部定义的方法不可枚举,但ES5中可枚举
19.7 添加私有方法
19.13 静态方法
19.15 new.target
该属性一般用于构造函数中,返回new 命令作用于的那个构造函数。如果构造函数不是通过 new 命令调用的会返回 undefined
注:子类继承父类时,new.target 会返回子类;在函数外部,使用该属性会报错
根据其特性,可写出不能独立使用、必须继承后才能使用的类
20.1 继承
class Parent {
static myMethod(msg) {
console.log('static', msg);
}
myMethod(msg) {
console.log('instance', msg);
}
}
class Child extends Parent {
static myMethod(msg) {
super.myMethod(msg);
}
myMethod(msg) {
super.myMethod(msg);
}
}
Child.myMethod(1); // static 1
//此处是类自身调用私有方法,即 static myMethod,然后静态方法中的 super 指向的是父类本身的私有方法。
var child = new Child();
child.myMethod(2); // instance 2
//此处是实例对象调用原型方法,即 myMethod,然后该普通方法中的 super 指向的是父类的原型对象。
20.4 类的 prototype 属性和 __proto__ 属性
//继承的原理
class A { }
class B { }
// B 的实例继承 A 的实例
Object.setPrototypeOf(B.prototype, A.prototype);
// B 的实例继承 A 的静态属性
Object.setPrototypeOf(B, A);
const b = new B();
//setPrototypeOf 的原理
Object.setPrototypeOf = function (obj, proto) {
obj.__proto__ = proto;
return obj;
}
//so...
class A { }
class B extends A { }
let a = new A();
let b = new B();
B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true
b.__proto__.__proto__ === a.__proto__ //true
总结:
extends 的继承目标
class B extends A{} A只要是有 prototype 属性的函数就行,即除了Function.prototype函数,A可以是任意函数
//情况一
class A extends Object { }
A.__proto__ === Object // true
A.prototype.__proto__ === Object.prototype // true
//这种情况下,A其实就是构造函数Object的复制,A的实例就是Object的实例。
//情况二
class A { }
A.__proto__ === Function.prototype // true
A.prototype.__proto__ === Object.prototype // true
//这种情况下,A作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承Function.prototype。但是,A调用后返回一个空对象(即 Object实例),所以A.prototype.__proto__指向构造函数(Object)的prototype属性。
//情况三
class A extends null { }
A.__proto__ === Function.prototype // true
A.prototype.__proto__ === undefined // true
//这种情况与第二种情况非常像。A也是一个普通函数,所以直接继承Function.prototype。但是,A调用后返回的对象不继承任何方法,所以它的 __proto__指向Function.prototype,即实质上执行了下面的代码。
class C extends null {
constructor() {
return Object.create(null);
}
}
20.6 Mixin 多个对象合成一个新的对象,新兑现具有各个成员的接口
function mix(...mixins) {
class Mix {}
for (let mixin of mixins) {
copyProperties(Mix, mixin); // 拷贝实例属性
copyProperties(Mix.prototype, mixin.prototype); // 拷贝原型属性
}
return Mix;
}
function copyProperties(target, source) {
for (let key of Reflect.ownKeys(source)) {
if ( key !== "constructor" && key !== "prototype"&& key !== "name") {
let desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}
//上面代码的mix函数,可以将多个对象合成为一个类。使用的时候,只要继承这个类即可。
class DistributedEdit extends mix(Loggable, Serializable) { // ... }
以 Mixin 为例
//mixins
export function mixins(...list) {
return function (target) {
Object.assign(target.prototype, ...list);
};
}
//可将上述方法作为修饰器,为类加入新方法
import { mixins } from './mixins';
const Foo = {
foo() {
console.log('foo')
}
};
@mixins(Foo)
class MyClass {}
let obj = new MyClass();
obj.foo() // "foo"
22.1 概述
ES6 实现的模块相当简单,完全可以取代 CommonJs 和 AMD 规范,成为服务器和浏览器通用的模块解决方案
其模块设计思想是尽量静态化,使得编译时就能确定模块的依赖关系及输入输出变量,效率更高
而 CommonJs 和 AMD 只能在运行时确定。例:
// CommonJS模块
let { stat, exists, readFile } = require('fs');
// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;
//整体加载 fs 模块(所有方法),生成一个对象(_fs),然后在该对象上读取相应方法,这种加载称为“运行时加载”,因为只有运行时才能得到这个对象
ES6 模块的优势:
1. 编译时加载使得能够静态分析,从而进一步拓宽 JS 语法
2. 不再需要UMD模块格式,将来服务器和浏览器都会支持 ES6 模块格式
3. 将来浏览器的新 API 就能用模块格式提供,不再必须做成全局变量或者navigator对象的属性
4. 不再需要对象作为命名空间(比如Math对象),未来这些功能可以通过模块提供
22.2 严格模式
ES6 默认采用严格模式,不管有没有加 “use strict”
严格模式限制:
22.3 export 命令
export 用于规定模块的对外接口,import 用于引入其他模块
export 两种写法:
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
//或
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {firstName, lastName, year}; //即使只有一个变量也要用{}包裹,这样才形成一个接口
//默认输出
export default function AA(){} //default本质上是个变量 后面不能跟变量声明
import aa from 'AA' //当导出的是default 时,import中可以自定义名称表示输出的内容
//一个模块只能有一个默认输出,所以import只能对应一个方法,不需要{}
//正常输出
export function AA(){}
import {AA} from 'AA' //注意大括号
export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值,但 import 里不允许运行时改变引用的对象
CommonJS 模块输出的是值的缓存,不存在动态更新
export 不能放在块级作用于中,会报错,因为处于条件代码块中不能做静态优化,违背了设计初衷
22.4 import 命令
由于import 命令式再编译阶段执行的,所以会变量提升,且不能使用表达式和变量。
其基本语法:
import { lastName as surname } from './profile';
// as 用于重命名
// from 指定引用的文件的位置,文件类型可省
import * from './profile';
//* :表示整体加载,包含所有输出值,且引入的该对象是静态分析的,不允许运行时改变
//复合使用
export { foo, bar } from 'my_module';
// 等同于
import { foo, bar } from 'my_module';
export { foo, bar };
多次引用同一文件只会执行一次
22.10 import()
//加载多个模块
Promise.all([
import('./module1.js'),
import('./module2.js'),
import('./module3.js'),
])
.then(([module1, module2, module3]) => {
···
});
23.1 浏览器加载
传统方法:
浏览器按顺序同步加载 JavaScript 脚本
通过添加 defer 、async实现异步加载,避免浏览器堵塞
defer:渲染完后再执行,按写入顺序执行
async:下载完就执行,按下载完成顺序执行,渲染引擎可能会中断渲染
加载规则 :
浏览器加载 ES6 模块,要加入type="module"属性,这时,浏览器知道是ES6 模块,而进行异步加载,且按页面顺序执行,相当于defer
对于外部的模块脚本,有几点需要注意:
23.2. ES6 模块与 CommonJS 模块
主要差异:
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
CommonJS 模块输出的是原始类型的值时会被缓存,输出函数能得到内部变动后的值
CommonJS 模块加载机制:CommonJS 的一个模块,就是一个脚本文件。require命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象
ES6 运行机制:JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行 时,再根据这个只读引用,到被加载的那个模块里面去取值。
export通过接口,输出的是同一个值。不同的脚本加载这个接口,得到的都是同样的实例
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
23.3. Node 加载
Node 要求 ES6 模块采用 .mjs 后缀文件名。只要脚本文件里面使用 import 或者 export 命令,那么就必须采用 .mjs 后缀名,require 不能混入使用
ES6 模块加载 CommonJS 模块
CommonJS 模块里的 module.exports 相当于 export default
CommonJS 模块的输出缓存机制,在 ES6 加载方式下依然有效。
//ES6 加载 CommonJS 的不正确的写法 ,readfile方法只有在运行时才获取得到,而import在编译时就执行了
import { readfile } from 'fs';
CommonJS 模块加载 ES6 模块
CommonJS 模块加载 ES6 模块,不能使用require命令,而要使用import()函数。ES6 模块的所有输出接口,会成为输入对象的属性
24.1 块级作用域
24.2 字符串
静态字符串一律使用单引号或者反引号,不使用双引号
动态字符串使用反引号
24.3 解构赋值
// bad
function processInput(input) {
return [left, right, top, bottom];
}
// good
function processInput(input) {
return { left, right, top, bottom };
}
const { left, right } = processInput(input);
24.4~9
// good
const a = { k1: v1, k2: v2 }; //最后成员无逗号
const b = {
k1: v1,
k2: v2, //最后成员有逗号
};
//2.
// bad
const a = {};
a.x = 3;
// if reshape unavoidable
const a = {};
Object.assign(a, { x: 3 });
// good
const a = { x: null };
a.x = 3;
//5.使用扩展运算符(...)拷贝数组
const itemsCopy = [...items];
//3. 函数
//立即执行函数可以写成箭头函数的形式。
(() => {
console.log('Welcome to the Internet.');
})();
25.2 相等运算符(==)
算法说明:
1. 如果x不是正常值(比如抛出一个错误),中断执行。
2. 如果y不是正常值,中断执行。
3. 如果Type(x)与Type(y)相同,执行严格相等运算x === y。
4. 如果x是null,y是undefined,返回true。
5. 如果x是undefined,y是null,返回true。
6. 如果Type(x)是数值,Type(y)是字符串,返回x == ToNumber(y)的结果。
7. 如果Type(x)是字符串,Type(y)是数值,返回ToNumber(x) == y的结果。
8. 如果Type(x)是布尔值,返回ToNumber(x) == y的结果。
9. 如果Type(y)是布尔值,返回x == ToNumber(y)的结果。
10. 如果Type(x)是字符串或数值或Symbol值,Type(y)是对象,返回x == ToPrimitive(y)的结果。
11. 如果Type(x)是对象,Type(y)是字符串或数值或Symbol值,返回ToPrimitive(x) == y的结果。
12. 返回false。