资讯专栏INFORMATION COLUMN

ES6学习笔记1--let和const命令、解构赋值和Symbol

liaosilzu2007 / 1255人阅读

摘要:和命令命令在声明所在的块级作用域内有效。解构赋值从数组和对象中提取值,对变量进行赋值,这被称为解构。数值和布尔值的解构解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。默认值解构赋值允许指定默认值。

let和const命令 let命令

在声明所在的块级作用域内有效。

只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。

在同一个作用域内,不允许重复声明变量。var可以重复声明。

let命令不存在变量提升。var命令会发生”变量提升“现象。因此使用let命令声明的变量,凡是在声明之前就使用这些变量,就会报错。

块级作用域与函数声明

在块级作用域之中声明的函数,在块级作用域之外不可引用。

在浏览器的 ES6 环境中,块级作用域内声明的函数,行为类似于var声明的变量。

ES6 的块级作用域允许声明函数的规则,只在使用大括号的情况下成立,如果没有使用大括号,就会报错。
应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。

const

const声明的变量不得改变值。只声明不赋值,就会报错。

在声明所在的块级作用域内有效。

不存在变量提升。

在同一个作用域内,不允许重复声明变量。

const实际上保证的,是变量指向的那个内存地址不得改动。因此:

对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。

对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const只能保证这个指针是固定的。

例如:

const foo = {};

// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123

// 将 foo 指向另一个对象,就会报错。因为常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。
foo = {}; // TypeError: "foo" is read-only
顶层对象的属性

var命令和function命令声明的全局变量是顶层对象的属性,即顶层对象的属性与全局变量是等价的。

let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。

解构赋值

从数组和对象中提取值,对变量进行赋值,这被称为解构。解构赋值左边定义了要从原变量中取出什么变量。解构赋值允许,等号左边的模式之中,不放置任何变量名。

解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。

let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError
数组解构

数组的元素是按次序排列的,变量的取值由它的位置决定。

let [a, b, c] = [1, 2, 3];//从数组中提取值,按照对应位置,对变量赋值

本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。如果解构不成功,变量的值就等于undefined。

不完全解构:等号左边的模式,只匹配一部分的等号右边的数组。

let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4

只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。

对象解构

对象解构需变量与属性同名,才能取到正确的值。

let { foo,bar } = { foo: "aaa", bar: "bbb" };
//是let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };的简写形式
foo // "aaa"
bar // "bbb"

let { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined

如果变量名与属性名不一致,必须写成下面这样。

let { foo: baz } = { foo: "aaa", bar: "bbb" };
baz // "aaa"
foo // error: foo is not defined
//foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo。

以下语句中变量的声明和赋值是一体的

let {foo} = {foo: 1};

let命令下面一行的圆括号是必须的,否则会报错。因为解析器会将起首的大括号,理解成一个代码块,而不是赋值语句。

let foo;
({foo} = {foo: 1}); // 成功

通过解构可以无需声明来赋值一个变量。

({a, b} = {a: 1, b: 2})
//等同于
var {a, b} = {a: 1, b: 2}

数组本质是特殊的对象,因此可以对数组进行对象属性的解构。

let arr = [1, 2, 3];
let {0 : first, [arr.length - 1] : last} = arr;//方括号这种写法,属于“属性名表达式”
first // 1
last // 3
字符串解构

字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。

const [a, b, c, d, e] = "hello";
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。

let {length : len} = "hello";
len // 5
数值和布尔值的解构

解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。

let {toString: s} = 123;
s === Number.prototype.toString // true

let {toString: s} = true;
s === Boolean.prototype.toString // true

//数值和布尔值的包装对象都有toString属性,因此变量s都能取到值.
函数参数的解构赋值

函数的参数也可以使用解构赋值。

function add([x, y]){
  return x + y;
}

add([1, 2]); // 3

上面代码中,函数add的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x和y。对于函数内部的代码来说,它们能感受到的参数就是x和y。

默认值

解构赋值允许指定默认值。如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。

let [x, y = "b"] = ["a"]; // x="a", y="b"

var {x, y = 5} = {x: 1};
x // 1
y // 5

ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,如果一个数组成员或对象的属性值不严格等于undefined,默认值是不会生效的

let [x = 1] = [undefined];
x // 1

let [x = 1] = [null];
x // null
//数组成员是null,默认值就不会生效,因为null不严格等于undefined。

var {x = 3} = {x: null};
x // null

函数参数的解构也可以使用默认值。以下两种写法不一样。

function move({x = 0, y = 0} = {}) {
  return [x, y];
}
move({x: 3}); // [3, 0]
//为变量x和y指定默认值
function move({x, y} = { x: 0, y: 0 }) {
  return [x, y];
}
move({x: 3}); // [3, undefined]
//为函数move的参数指定默认值
模式不能使用圆括号

只有赋值语句非模式部分,可以使用圆括号。

({ p: (d) } = {}); // 正确
let [(a)] = [1];//报错   因为变量声明语句中,不能带有圆括号。
function f([(z)]) { return z; }// 报错 因为函数参数也属于变量声明,因此不能带有圆括号。


[(b)] = [3]; // 正确 因为模式是取数组的第一个成员,跟圆括号无关。
([a]) = [5]; //报错 因为赋值语句中,不能将整个模式,或嵌套模式中的一层,放在圆括号之中。
Symbol

ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。Symbol值通过Symbol函数生成。对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

注意,Symbol函数前不能使用new命令,否则会报错。这是因为 Symbol 值不是对象,所以也不能添加属性。基本上,它是一种类似于字符串的数据类型。Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述。如果 Symbol 的参数是一个对象,就会调用该对象的toString方法,将其转为字符串,然后才生成一个 Symbol 值。

var s1 = Symbol("foo");
var s2 = Symbol("bar");

s1 // Symbol(foo)
s2 // Symbol(bar)

s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"

注意,Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的

// 没有参数的情况
var s1 = Symbol();
var s2 = Symbol();

s1 === s2 // false

// 有参数的情况
var s1 = Symbol("foo");
var s2 = Symbol("foo");

s1 === s2 // false

Symbol 值不能与其他类型的值进行运算,会报错。但是,Symbol 值可以显式转为字符串或布尔值,但是不能转为数值。

var sym = Symbol("My symbol");

"your symbol is " + sym
// TypeError: can"t convert symbol to string

String(sym) // "Symbol(My symbol)"
Boolean(sym) // true
Number(sym) // TypeError
作为属性名的 Symbol

由于每一个 Symbol 值都是不相等的,因此用于对象的属性名时,就能保证不会出现同名的属性。

var mySymbol = Symbol();

// 第一种写法
var a = {};
a[mySymbol] = "Hello!";

// 第二种写法
var a = {
  [mySymbol]: "Hello!"
};

// 第三种写法
var a = {};
Object.defineProperty(a, mySymbol, { value: "Hello!" });

// 以上写法都得到同样结果
a[mySymbol] // "Hello!"

使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。该属性仍是公开属性。因此,Symbol 值作为对象属性名时,不能用点运算符。

var mySymbol = Symbol();
var a = {};

a.mySymbol = "Hello!";
a[mySymbol] // undefined
a["mySymbol"] // "Hello!"

上面代码中,因为点运算符后面总是字符串,所以不会读取mySymbol作为标识名所指代的那个值,导致a的属性名实际上是一个字符串,而不是一个 Symbol 值。

Symbol 类型还可以用于定义一组常量,保证其值得唯一。Symbol 作为属性名,该属性不会出现在for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。可使用Object.getOwnPropertySymbols方法获取对象的所有 Symbol 属性名。

Symbol.for(),Symbol.keyFor()

Symbol.for():会先检查给定的key是否已经存在,如果不存在才会新建一个值,该值会被登记在全局环境中供搜索。
Symbol():没有登记制度,因此每次调用都会返回一个不同的值。

Symbol.for("bar") === Symbol.for("bar")
// true

Symbol("bar") === Symbol("bar")
// false


var s1 = Symbol("foo");
var s2=Symbol.for("foo");
s1===s2 //false

Symbol.keyFor():返回一个已登记的 Symbol 类型值的key。

var s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"

var s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined 变量s2属于未登记的Symbol值,所以返回undefined。

Symbol.for为Symbol值登记的名字,是全局环境的,可以在不同的 iframe 或 service worker 中取到同一个值。

内置的Symbol值

Symbol.hasInstance:对象的Symbol.hasInstance属性,指向一个内部方法。当其他对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法。

Symbol.isConcatSpreadable:对象的Symbol.isConcatSpreadable属性等于一个布尔值,表示该对象使用Array.prototype.concat()时,是否可以展开。数组的默认行为是可以展开。类似数组的对象的默认行为是不可以展开。Symbol.isConcatSpreadable属性等于true或undefined时,都是可以展开的。对于一个类来说,Symbol.isConcatSpreadable属性必须写成实例的属性。

let arr1 = ["c", "d"];
["a", "b"].concat(arr1, "e") // ["a", "b", "c", "d", "e"]
arr1[Symbol.isConcatSpreadable] // undefined

let obj = {length: 2, 0: "c", 1: "d"};
["a", "b"].concat(obj, "e") // ["a", "b", obj, "e"]
obj[Symbol.isConcatSpreadable] = true;
["a", "b"].concat(obj, "e") // ["a", "b", "c", "d", "e"]

Symbol.species:对象的Symbol.species属性,指向当前对象的构造函数。创造实例时,默认会调用这个方法,即使用这个属性返回的函数当作构造函数,来创造新的实例对象。

Symbol.match:对象的Symbol.match属性,指向一个函数。当执行str.match(myObject)时,如果该属性存在,会调用它,返回该方法的返回值。

String.prototype.match(regexp)
// 等同于
regexp[Symbol.match](this)

Symbol.replace:对象的Symbol.replace属性,指向一个方法,当该对象被String.prototype.replace方法调用时,会返回该方法的返回值。

String.prototype.replace(searchValue, replaceValue)
// 等同于
searchValue[Symbol.replace](this, replaceValue)

Symbol.search:对象的Symbol.search属性,指向一个方法,当该对象被String.prototype.search方法调用时,会返回该方法的返回值。

String.prototype.search(regexp)
// 等同于
regexp[Symbol.search](this)

Symbol.split:对象的Symbol.split属性,指向一个方法,当该对象被String.prototype.split方法调用时,会返回该方法的返回值。

String.prototype.split(separator, limit)
// 等同于
separator[Symbol.split](this, limit)

Symbol.iterator:对象的Symbol.iterator属性,指向该对象的默认遍历器方法。

Symbol.toPrimitive:对象的Symbol.toPrimitive属性,指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。

Symbol.toStringTag:对象的Symbol.toStringTag属性,指向一个方法。在该对象上面调用Object.prototype.toString方法时,如果这个属性存在,它的返回值会出现在toString方法返回的字符串之中,表示对象的类型。也就是说,这个属性可以用来定制[object Object]或[object Array]中object后面的那个字符串。

Symbol.unscopables:对象的Symbol.unscopables属性,指向一个对象。该对象指定了使用with关键字时,哪些属性会被with环境排除。

参考自:ECMAScript 6 入门

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/83037.html

相关文章

  • ES6

    摘要:情况一情况二这两种情况,根据的规定都是非法的。的作用域与命令相同只在声明所在的块级作用域内有效。因此,将一个对象声明为常量必须非常小心。顶层对象的属性与全局变量挂钩,被认为时语言最大的设计败笔之一。 这是ES6的入门篇教程的笔记,网址:链接描述,以下内容中粗体+斜体表示大标题,粗体是小标题,还有一些重点;斜体表示对于自身,还需要下功夫学习的内容。这里面有一些自己的见解,所以若是发现问题...

    AZmake 评论0 收藏0
  • ES6学习总结(1

    摘要:返回一个对象,遍历对象自身和继承的所有可枚举属性不含,与相同和在红宝书中就已经提到过属性,表示的是引用类型实例的一个内部指针,指向该实例的构造函数的原型对象。 半个月前就决定要将ES6的学习总结一遍,结果拖延症一犯,半个月就过去了,现在补起来,惭愧惭愧。 阮一峰的《ES6标准入门》这本书有300页左右,除了几个新的API和js语法的扩展,真正有价值的内容并不多。所谓存在即合理,每部分的...

    happyfish 评论0 收藏0
  • es6 学习笔记

    摘要:块级作用域只有全局作用域和函数作用域,没有块级作用域,这带来了很多不合理的场景。如声明变量的方法只有两种声明变量的方法命令一共有六种命令变量的解构赋值允许按照一定的模式,从数组和对象中提取,按照位置的对应关系对变量赋值,这被称为解构。 块级作用域 es5只有全局作用域和函数作用域,没有块级作用域,这带来了很多不合理的场景。 第一种场景:内层变量可能会覆盖外层变量 var test = ...

    李昌杰 评论0 收藏0
  • ES6(中)

    摘要:它用来比较两个值是否严格相等,与严格比较运算符的行为基本一致。两个对象的地址不一样与严格比较运算符的不同之处只有两个一是不等于,二是等于自身基本用法方法用于对象的合并,将源对象的所有可枚举属性,赋值到目标对象。 这是ES6的入门篇教程的笔记,网址:链接描述,以下内容中粗体+斜体表示大标题,粗体是小标题,还有一些重点;斜体表示对于自身,还需要下功夫学习的内容。这里面有一些自己的见解,所以...

    dreamGong 评论0 收藏0
  • 学习ES6笔记──工作中常用到的ES6语法

    摘要:但是有了尾调用优化之后,递归函数的性能有了提升。常被用来检查对象中是否存在某个键名,集合常被用来获取已存的信息。循环解构对象本身不支持迭代,但是我们可以自己添加一个生成器,返回一个,的迭代器,然后使用循环解构和。 一、let和const 在JavaScript中咱们以前主要用关键var来定义变量,ES6之后,新增了定义变量的两个关键字,分别是let和const。对于变量来说,在ES5中...

    curried 评论0 收藏0

发表评论

0条评论

liaosilzu2007

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<