let命令
- let命令与var(全局范围内有效)命令类似,但let命令申明的变量只在所在代码块有效。
- let命令不存在变量提升,所以会报错;
- 暂时性死区:let命令申明变量之前,变量不可用
- 也说明了typeof并不是完全安全。若去掉申明语句 let则正确。
typeof x; // ReferenceError
let x;
- y赋值给x,但是此时y并未定义,所以报错
function bar(x = y, y = 2) {
return [x, y];
}
bar(); // 报错
function bar(x = 2, y = x) {
return [x, y];
}
bar(); // [2, 2]
- let申明的变量只在当前代码块有效,let申明的i,i只在本轮循环中有效,所以,每轮循环都重新定义i。若是重新定义i又怎么知道上一轮i的值?这是由于javascript引擎内部记住上一轮的值,在本轮初始化时,在上一轮的基础上计算。
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
变量不可在同一区域重复申明。
- 块作用域
- 块级作用域允许任意嵌套;
- 外层定义域无法获取内层定义域变量;
- 块级定义域内函数的定义相当于let申明,对作用域外没有影响。
在浏览器的 ES6 环境中,块级作用域内声明的函数,行为类似于var声明的变量。
ES6 的块级作用域允许声明函数的规则,只在使用大括号的情况下成立,如果没有使用大括号,就会报错。
// 不报错
'use strict';
if (true) {
function f() {}
}
// 报错
'use strict';
if (true)
function f() {}
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f();
}());
// Uncaught TypeError: f is not a function
上面的代码在符合 ES6 的浏览器中,都会报错,因为实际运行的是下面的代码。
function f() { console.log('I am outside!'); }
(function () {
var f = undefined;
if (false) {
function f() { console.log('I am inside!'); }
}
f();
}());
// Uncaught TypeError: f is not a function
应该写成函数表达式,而不是函数声明语句。
// 函数声明语句
{
let a = 'secret';
function f() {
return a;
}
}
// 函数表达式
{
let a = 'secret';
let f = function () {
return a;
};
}
const
- const申明一个只读的常量。一旦申明,值不再改变。
- const申明时必须赋值,否则报错。
- cosnt是块级作用域,没有申明提升。
- const存在暂时性死区。
- 不可重复定义。
- 本质:const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。
const a = [];
a.push('Hello'); // 可执行
a.length = 0; // 可执行
a = ['Dave']; // 报错
冻结对象
const foo = Object.freeze({});
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;
es6申明变量的方法
- var,function()
- let, const
- import,class
顶层对象
- 顶层对象,在浏览器环境中是window,在node中是Global。
es5中,顶层对象与全局变量是等价的。 - es6规定:ES6 为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。
Global对象
es5在各种实现里面是不统一的,所以
现在有一个提案,在语言标准的层面,引入global
作为顶层对象。也就是说,在所有环境下,global
都是存在的,都可以从它拿到顶层对象。
垫片库system.global
模拟了这个提案,可以在所有环境拿到global
。
// CommonJS 的写法
require('system.global/shim')();
// ES6 模块的写法
import shim from 'system.global/shim'; shim();
上面代码可以保证各种环境里面,global
对象都是存在的。
// CommonJS 的写法
var global = require('system.global')();
// ES6 模块的写法
import getGlobal from 'system.global';
const global = getGlobal();
上面代码将顶层对象放入变量global
。
变量的解构赋值
- 数组的解构赋值:从数组中提取值,赋值给变量。
解构不成功,变量的值就等于undefined。
let [foo] = [];
let [bar, foo] = [1];
以上两种情况都属于解构不成功,foo的值都会等于undefined。
- 数组的不完全解构,即等号左边只匹配右边数组的一部分。
- 等号右边不是数组(不可遍历的结构),将会报错。
因为等号右边的值,要么转为对象以后不具备 Iterator 接口(前五个表达式),要么本身就不具备 Iterator 接口(最后一个表达式)。
// 报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};
- set结构也可以使用数组解构赋值。
- 实际,只要是具有iterator接口的数据结构都可以使用数组解构。
function* fibs() {
let a = 0;
let b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
let [first, second, third, fourth, fifth, sixth] = fibs();
sixth // 5
上面代码中,fibs是一个 Generator 函数,原生具有 Iterator 接口。解构赋值会依次从这个接口获取值。
ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined,默认值才会生效。
默认值可以引用解构赋值的其他变量,但该变量必须已经声明。
let [x = 1, y = x] = []; // x=1; y=1
let [x = 1, y = x] = [2]; // x=2; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = []; // ReferenceError: y is not defined
- 对象的解构赋值
- 数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值
- 对象的属性名和变量名不一致可以采用这种形式:
let { foo: foo1, bar: bar1 } = { foo: "aaa", bar: "bbb" };
对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。 - 下面代码有三次解构赋值,分别是对loc、start、line三个属性的解构赋值。注意,最后一次对line属性的解构赋值之中,只有line是变量,loc和start都是模式,不是变量。
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时,默认值生效。
- 数组本质是特殊的对象,可以使用对象的解构赋值。
字符串解构赋值
- 字符串此时被转换为一个类似数组的对象。
- 类似数组的对象都有一个length属性,可以解构赋值。
数值和布尔值解构赋值
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
上面代码中,数值和布尔值的包装对象都有toString属性,因此变量s都能取到值。
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError
函数的解构赋值
function move({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]
原括号问题
不能使用原括号的解构赋值。
- 变量声明语句,模式不能使用圆括号。
// 全部报错
let [(a)] = [1];
let {x: (c)} = {};
let ({x: c}) = {};
let {(x: c)} = {};
let {(x): c} = {};
let { o: ({ p: p }) } = { o: { p: 2 } };
- 函数参数也属于变量声明,不能使用圆括号。
// 报错
function f([(z)]) { return z; }
// 报错
function f([z,(x)]) { return x; }
3.赋值语句的模式(不能扩住模式,部分或整体)
({ p: a }) = { p: 42 };
([a]) = [5];
上面代码将整个模式放在圆括号之中,导致报错。
// 报错
[({ p: a }), { x: c }] = [{}, {}];
上面代码将一部分模式放在圆括号之中,导致报错。
可以使用圆括号的情况只有一种:
赋值语句的非模式部分,可以使用圆括号。
[(b)] = [3]; // 正确
({ p: (d) } = {}); // 正确
[(parseInt.prop)] = [3]; // 正确
解构赋值的应用
- 交换俩数数值
- 函数的参数
- 遍历map
- 获取json数据
- 函数的默认值
字符串的扩展
字符的unicode表示法
- \uxxxx表示一个字符,xxxx表示unicode码点。
- 只限于码点在\u0000~\uffff之间的字符,超出范围的用俩个双字节表示。
- 直接在\u后面跟上超过0xFFFF的数值(比如\u20BB7),JavaScript 会理解成\u20BB+7。由于\u20BB是一个不可打印字符,所以只会显示一个空格,后面跟着一个7。
"\uD842\uDFB7"
// ""
"\u20BB7"
// " 7"
- **es6表示,只要将码点放入大括号,就能正确解读该字符。
"\u{20BB7}"
// ""
"\u{41}\u{42}\u{43}"
// "ABC"
let hello = 123;
hell\u{6F} // 123
'\u{1F680}' === '\uD83D\uDE80'
// true
codepointAt
由于字符按照utf-16来存储,每个字符固定2个字节,4个字节存储的字符js默认是俩个字符。
var s = "";
s.length // 2
s.charAt(0) // ''
s.charAt(1) // ''
s.charCodeAt(0) // 55362
s.charCodeAt(1) // 57271
上面代码中,汉字“”(注意,这个字不是“吉祥”的“吉”)的码点是0x20BB7,UTF-16 编码为0xD842 0xDFB7(十进制为55362 57271),需要4个字节储存。对于这种4个字节的字符,JavaScript 不能正确处理,字符串长度会误判为2,而且charAt方法无法读取整个字符,charCodeAt方法只能分别返回前两个字节和后两个字节的值。
es6提供了codePointAt方法,能正确识别4个字节存储的字符,并返回码点。
let s = 'a';
s.codePointAt(0) // 134071
s.codePointAt(1) // 57271
s.codePointAt(2) // 97
codePointAt会返回10进制的码点,需要16进制可以通过.tostring(16)
来转换,对于2个字节存储的字符charCodeAt和codePointAt返回结果相同。
但是codePointAt的参数任然是错误的,可以通过for-of 来循环。
判断一个字符是2字节存储还是4字节存储
function is32Bit(c) {
return c.codePointAt(0) > 0xFFFF;
}
is32Bit("") // true
is32Bit("a") // false
String.fromCodePoint()
es5提供了方法String.fromCharCode()方法,用于从码点返回字符,但是不能识别32位的utf-16字符(unicode>0xffff)
String.fromCharCode(0x20BB7)
// "ஷ"
上面代码中,String.fromCharCode不能识别大于0xFFFF的码点,所以0x20BB7就发生了溢出,最高位2被舍弃了,最后返回码点U+0BB7对应的字符,而不是码点U+20BB7对应的字符。
es6提供了String.fromCodePoint()方法,可以识别32位的字符。
String.fromCodePoint(0x20BB7)
// ""
String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y'
// true
- 如果String.fromCodePoint()方法含有多个参数,则他们会被合并为一个字符串返回。
- 注意:fromCodePoint()方法定义在String对象上,而codePointAt()方法则被定义在字符串的实例对象上。
字符串遍历器接口
es6为字符串遍历提供了遍历器接口,可以通过for-of来循环遍历字符串。
for (let codePoint of 'foo') {
console.log(codePoint)
}
// "f"
// "o"
// "o"
除了遍历字符串,这个遍历器最大的优点是可以识别大于0xFFFF的码点,传统的for循环无法识别这样的码点。
let text = String.fromCodePoint(0x20BB7);
for (let i = 0; i < text.length; i++) {
console.log(text[i]);
}
// " "
// " "
for (let i of text) {
console.log(i);
}
// ""
上面代码中,字符串text只有一个字符,但是for循环会认为它包含两个字符(都不可打印),而for...of循环会正确识别出这一个字符。
at()方法
es5为字符串提供了CharAt()方法,用来返回指定位置的字符。不能识别超过(0xffff)的字符。
目前有一个提案,用字符串实例的at()方法来返回大于0xffff的字符。
'abc'.at(0) // "a"
''.at(0) // ""
normalize()
合成字符(在语义和视觉上等价),javascript不能识别。
es6规定normalize,将字符的不同表示方法统一为相同样式,为unicoded标准化。
4个参数:
- NFC:在标准等价基础上,返回多个字符合成的字符。
- NFD:在标准等价基础上,返回分解合成字符的简单字符。
- NFKC:在兼容等价合成,返回合成字符。
- NFKD:在兼容等价合成,返回分解合成字符的多个简单字符。
标准等价合成:在语义和视觉上等价。
兼容等价合成:在语义等价,视觉不等价。
注意:normalize不能识别3个或3个以上的合成字符,只能用正则表达式,unicoded 区间判断。
用来确定字符串是否包含另一字符串
- javascript中的indexOf()方法
- es6中的方法:
- includes():返回一个布尔值,表示是否找到参数字符串。
- startsWith():返回一个布尔值,表示是否原字符串头部。
- endsWith():返回一个布尔值,是否含在原字符串末尾。
第二个参数n,表示开始搜索的位置。
endsWith()针对前n个字符,其他表示从第n个位置开始到字符串结束。
repeat()返回一个新字符串,参数n表示将原字符串重复几次
- 参数是小数会向小取整;
- 参数是负数或者Infinity,报错;
- 参数在0~-1之间,会向下取整为-0,视为0;
- NaN等价与0;
- 参数是字符串会转换为数字。
padStart(),padEnd()
es2017引入字符串补全功能:如果字符串的长度小于指定最小长度,会在头部尾部补全。
- padStart():头部补全;
- padEnd():尾部补全;
- 接受俩个参数,一个为字符串的最小长度,第二个是补全字符串;
- 若原字符串的长度大于等于最小长度,则返回原字符串;
- 补全字符串和原字符串的长度之和大于最小长度,则截取超出位数的补全字符串。
- 省略第二个参数,默认使用空格补全;
用途:1. 为数值补全指定位数;
- 提示字符串格式
'1'.padStart(10, '0') // "0000000001"
'12'.padStart(10, '0') // "0000000012"
'123456'.padStart(10, '0') // "0000123456"
'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"
matchAll()
matchAll方法返回一个正则表达式在当前字符串的所有匹配,
模板字符串
传统的 JavaScript 语言,输出模板通常是这样写的。
$('#result').append(
'There are ' + basket.count + ' ' +
'items in your basket, ' +
'' + basket.onSale +
' are on sale!'
);
上面这种写法相当繁琐不方便,ES6 引入了模板字符串解决这个问题。
$('#result').append(`
There are ${basket.count} items
in your basket, ${basket.onSale}
are on sale!
`);
模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
// 普通字符串
`In JavaScript '\n' is a line-feed.`
// 多行字符串
`In JavaScript this is
not legal.`
console.log(`string text line 1
string text line 2`);
// 字符串中嵌入变量
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`
- 在模板字符串中使用反引号,需要转义;
- 反引号表示字符串,会保留空格和换行,缩进,不想保留可以调用.trim();
- 模板字符串嵌入变量,引用变量需要将变量包含在${}之中;
- 大括号内可以放入javascript语句,运算,函数;
- 如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的toString方法。