微信公众号:[猫十二的日常],欢迎留言和指出问题a
let和const命令
let:它所声明的变量,只在本代码块内有效
for (let i = 0; i < 10; i++) {}
console.log(i);
//ReferenceError: i is not define
for循环还有一个特别之处,就是循环语句部分是一个父作用域,而循环体内部是一个单独的子作用域
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
不存在变量提升 let 它所声明的变量一定要在声明后使用,否则报错。
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;
// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
暂时性死区(temporal dead zone,简称 TDZ):只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响,对于typeof来说不再是安全的操作了
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
typeof x; // ReferenceError
let x;
不允许重复声明
// 报错
function () {
let a = 10;
var a = 1;
}
// 报错
function () {
let a = 10;
let a = 1;
}
const 命令
const声明一个只读的常量。一旦声明,常量的值就不能改变。
const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.
const的作用域与let命令相同:只在声明所在的块级作用域内有效。
if (true) {
const MAX = 5;
}
MAX // Uncaught ReferenceError: MAX is not defined
const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
if (true) {
console.log(MAX); // ReferenceError
const MAX = 5;
}
本质:实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
const a = [];
a.push('Hello'); // 可执行
a.length = 0; // 可执行
a = ['Dave']; // 报错
如果真的想将对象冻结,应该使用Object.freeze方法。
const foo = Object.freeze({});
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;
ES6 声明变量的六种方法
ES5 只有两种声明变量的方法:var命令和function命令。ES6除了添加let和const命令,后面章节还会提到,另外两种声明变量的方法:import命令和class命令。所以,ES6 一共有6种声明变量的方法。
数组的解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值
es5写法
let a = 1;
let b = 2;
let c = 3;
es6写法
let [a, b, c] = [1, 2, 3];
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3
let [ , , third] = ["foo", "bar", "baz"];
third // "baz"
let [x, , y] = [1, 2, 3];
x // 1
y // 3
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
let [x, y, ...z] = ['a'];
x // "a"
y // undefined //解构不成功,变量的值就等于undefined
z // []
不完全解构:即等号左边的模式,只匹配一部分的等号右边的数组
let [x, y] = [1, 2, 3];
x // 1
y // 2
let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4
解构的大前提在于必须具备 Iterator 接口 默认值:解构赋值允许指定默认值
let [foo = true] = [];
foo // true
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
//ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,如果一个数组成员不严格等于undefined,默认值是不会生效的
let [x = 1] = [undefined];
x // 1
let [x = 1] = [null];
x // null
对象的解构赋值
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值
let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
let { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
let { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined
变量名与属性名不一致,必须写成下面这样
var { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'
let { foo: baz } = { foo: "aaa", bar: "bbb" };
baz // "aaa"
foo // error: foo is not defined
//foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo。
采用这种写法时,变量的声明和赋值是一体的。对于let和const来说,变量不能重新声明,所以一旦赋值的变量以前声明过,就会报错。
let foo;
let {foo} = {foo: 1}; // SyntaxError: Duplicate declaration "foo"
let baz;
let {bar: baz} = {bar: 1}; // SyntaxError: Duplicate declaration "baz"
//let命令下面一行的圆括号是必须的,否则会报错。因为解析器会将起首的大括号,理解成一个代码块,而不是赋值语句。
let foo;
({foo} = {foo: 1}); // 成功
let baz;
({bar: baz} = {bar: 1}); // 成功
对象的解构也可以指定默认值。对象的属性值严格等于undefined
var {x = 3} = {};
x // 3
var {x, y = 5} = {x: 1};
x // 1
y // 5
var {x:y = 3} = {};
y // 3
var {x:y = 3} = {x: 5};
y // 5
var { message: msg = 'Something went wrong' } = {};
msg // "Something went wrong"
可以对现有对象赋值
let { log, sin, cos } = Math;
//Math对象的对数、正弦、余弦三个方法,赋值到对应的变量上,使用起来就会方便很多。
数组本质是特殊的对象,因此可以对数组进行对象属性的解构
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
只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError
函数参数的解构赋值
函数的参数也可以使用解构赋值
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
[[1, 2], [3, 4]].map(([a, b]) => a + b);
// [ 3, 7 ]
函数参数的解构也可以使用默认值
function move({x = 0, y = 0} = {}) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]
写法不同,结果不一样
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]
圆括号问题
解构赋值虽然很方便,但是解析起来并不容易。对于编译器来说,一个式子到底是模式,还是表达式,没有办法从一开始就知道,必须解析到(或解析不到)等号才能知道。由此带来的问题是,如果模式中出现圆括号怎么处理。ES6的规则是,只要有可能导致解构的歧义,就不得使用圆括号。但是,这条规则实际上不那么容易辨别,处理起来相当麻烦。因此,建议只要有可能,就不要在模式中放置圆括号。 变量声明语句中,不能带有圆括号。
// 全部报错:因为它们都是变量声明语句,模式不能使用圆括号
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; }
赋值语句中,不能将整个模式,或嵌套模式中的一层,放在圆括号之中。
// 全部报错
({ p: a }) = { p: 42 };
([a]) = [5];
[({ p: a }), { x: c }] = [{}, {}];
可以使用圆括号的情况
[(b)] = [3]; // 正确
({ p: (d) } = {}); // 正确
[(parseInt.prop)] = [3]; // 正确
//上面三行语句都可以正确执行,因为首先它们都是赋值语句,而不是声明语句;其次它们的圆括号都不属于模式的一部分。第一行语句中,模式是取数组的第一个成员,跟圆括号无关;第二行语句中,模式是p,而不是d;第三行语句与第一行语句的性质一致。
用途
(1)交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
(2)从函数返回多个值:函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便
// 返回一个数组
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
// 返回一个对象
function example() {
return {
foo: 1,
bar: 2
};
}
let { foo, bar } = example();
(3)函数参数的定义:解构赋值可以方便地将一组参数与变量名对应起来。
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);
// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});
(4)提取JSON数据:解构赋值对提取JSON对象中的数据,尤其有用
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]
(5)函数参数的默认值
jQuery.ajax = function (url, {
async = true,
beforeSend = function () {},
cache = true,
complete = function () {},
crossDomain = false,
global = true,
// ... more config
}) {
// ... do stuff
};
//指定参数的默认值,就避免了在函数体内部再写var foo = config.foo || 'default foo';这样的语句
(6)遍历Map结构:任何部署了Iterator接口的对象,都可以用for...of循环遍历。Map结构原生支持Iterator接口,配合变量的解构赋值,获取键名和键值就非常方便。
var map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
console.log(key + " is " + value);
}
// first is hello
// second is world
数组
es6加入了一些数组的新功能,以及改进了旧功能
保证永远传的是数组的元素
let items = Array.of(20);
console.log(items.length) //1
console.log(items[0]) //20
itmes = Array.of(2);
console.log(itmes.length);//1
console.log(itmes[0]); //2
itmes = Array.of('2');
console.log(itmes.length); //1
console.log(itmes[0]); //'2'
将类对象转换成数组的方式
function translate(){
return Array.from(arguments)
}
let number = translate(1,3,4,5);
console.log(number); //[ 1, 3, 4, 5 ]
映射转换(第二个参数的转换)
function translate(){
return Array.from(arguments,(value) => value+1)
}
let number = translate(1,3,4,5);
console.log(number); //[ 2, 4, 5, 6 ]
第三个值是this
let helper = {
diff:1,
add(value){
return value+this.diff;
}
}
function translate(){
return Array.from(arguments,helper.add,helper)
}
let number = translate(1,3,4,5);
console.log(number); // [ 2, 4, 5, 6 ]
Array.from可以用来转换可迭代对象
let number = {
*[Symbol.iterator](){
yield 1;
yield 2;
yield 3;
yield 4;
}
}
let number2 = Array.from(number,(value) => value+1);
console.log(number2); //[ 2, 3, 4, 5 ]
新增方法find()方法和findIndex()方法
两个方法的一个函数接受一个回调函数,以及一个this作为参数
find返回的是return为true的值
let numbers = [1,34,4,44,33,56];
console.log(numbers.find((item,index,arr) => {
if(item>33){
console.log(item,index,arr); //34 1 [ 1, 34, 4, 44, 33, 56 ]
return item
}
})) //34
findIndex()返回的是满足true的值的位置(第一个)
let numbers = [1,34,4,44,33,56];
console.log(numbers.findIndex((item,index,arr) => {
if(item>33){
console.log(item,index,arr); //34 1 [ 1, 34, 4, 44, 33, 56 ]
return item
}
})) //1
fill() 方法用一个固定值填充一个数组中从起始索引到终止索引内的全部元素
接受三个参数一个是要填充的值,一个是填充的起始位置,一个是结结束位置
let numbers = [1,34,4,44,33,56];
console.log(numbers.fill(1)) //[ 1, 1, 1, 1, 1, 1 ]
console.log(numbers) //[ 1, 1, 1, 1, 1, 1 ] //改变原数组
let nums = [2,3,4,5,2,4,3]
console.log(nums.fill(1,2)) //[ 2, 3, 1, 1, 1, 1, 1 ]
console.log(nums.fill(6,2,5)); //[ 2, 3, 6, 6, 6, 1, 1 ] //作闭右开
copyWith方法浅复制数组的一部分到同一数组中的另一个位置,并返回它,而不修改其大小
该方法接受3个值,一个是开始填充的位置,一个是开始复制的位置位置,改变原数组,一个是复制结束的位置
let numbers = [1,34,4,44,33,56];
console.log(numbers.copyWithin(2,3)); //[ 1, 34, 44, 33, 56, 56 ]
console.log(numbers.copyWithin(2,0,1)); //[ 1, 34, 1, 33, 56, 56 ]
定型数组
定型数组是一种用于处理数值类型(正如其名,不是所有类型)数据的专用数组,最早是在WebGL中使用的,WebGL是OpenGL ES 2.0的移植版,在Web 页面中通过 canvas标签 元素来呈现它。定型数组也被一同移植而来,其可为JS提供快速的按位运算
在JS中,数字是以64位浮点格式存储的,并按需转换为32位整数,所以算术运算非常慢,无法满足WebGL的需求。因此在ES6中引入定型数组来解决这个问题,并提供更高性能的算术运算。所谓定型数组,就是将任何数字转换为一个包含数字比特的数组,随后就可以通过我们熟悉的JS数组方法来进一步处理
数组缓冲区
ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区。ArrayBuffer 不能直接操作,而是要通过类型数组对象或 DataView 对象来操作,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。
let buffer = new ArrayBuffer(10);
console.log(buffer.byteLength); //10
//也可以使用数组的方法(类似)
let buffer2 = buffer.slice(1,3);
console.log(buffer2.byteLength) //2
不能修改缓存区的大小,只能修改数据缓冲区内的数据
通过视图操作数组缓冲区
数组缓冲区是内存中的一段地址,视图是用来操作内存的接口。视图可以操作数组缓冲区或缓冲区字节的子集,并按照其中一种数值型数据类型来读取和写入数据。DataView类型是一种通用的数组缓冲区视图,其支持所有8种数值型数据类型
- 有符号的8位整数(int8)
- 无符号的8位整数(uint8)
- 有符号的16位整数(int16)
- 无符号的16位整数(uint16)
- 有符号的32位整数(int32)
- 无符号的32位整数(uint32)
- 32位浮点数(float32)
- 64位浮点数(float64)
可以通过以下几种只读属性来获取视图的信息
- buffer 视图绑定的数组缓冲区
- byteOffset DataView构造函数的第二个参数,默认是0,只有传入参数时才有值
- byteLength DataView构造函数的第三个参数,默认是缓冲区的长度byteLength
let buffer = new ArrayBuffer(10),
//使用DataView创建视图的实例
view1 = new DataView(buffer),
view2 = new DataView(buffer,5,2); // 第一个参数是创建的缓冲区,第二个是操作缓冲区的起始位置,第三个参数是缓冲区的长度
console.log(view1.buffer === buffer); //true
console.log(view2.buffer === buffer); //true
console.log(view1.byteOffset); //0
console.log(view2.byteOffset); //5
console.log(view1.byteLength); //10
console.log(view2.byteLength); //2
读取和写入数据
js的8中数值型数据类型,在DataView的原型上都能找到
- 读方法 DataView.prototype.getInt8() 从DataView起始位置以byte为计数的指定偏移量(byteOffset)处获取一个8-bit数(一个字节). DataView.prototype.getUint8() 从DataView起始位置以byte为计数的指定偏移量(byteOffset)处获取一个8-bit数(无符号字节). DataView.prototype.getInt16() 从DataView起始位置以byte为计数的指定偏移量(byteOffset)处获取一个16-bit数(短整型). DataView.prototype.getUint16() 从DataView起始位置以byte为计数的指定偏移量(byteOffset)处获取一个16-bit数(无符号短整型). DataView.prototype.getInt32() 从DataView起始位置以byte为计数的指定偏移量(byteOffset)处获取一个32-bit数(长整型). DataView.prototype.getUint32() 从DataView起始位置以byte为计数的指定偏移量(byteOffset)处获取一个32-bit数(无符号长整型). DataView.prototype.getFloat32() 从DataView起始位置以byte为计数的指定偏移量(byteOffset)处获取一个32-bit数(浮点型). DataView.prototype.getFloat64() 从DataView起始位置以byte为计数的指定偏移量(byteOffset)处获取一个64-bit数(双精度浮点型).
- 写方法 DataView.prototype.setInt8() 从DataView起始位置以byte为计数的指定偏移量(byteOffset)处储存一个8-bit数(一个字节). DataView.prototype.setUint8() 从DataView起始位置以byte为计数的指定偏移量(byteOffset)处储存一个8-bit数(无符号字节). DataView.prototype.setInt16() 从DataView起始位置以byte为计数的指定偏移量(byteOffset)处储存一个16-bit数(短整型). DataView.prototype.setUint16() 从DataView起始位置以byte为计数的指定偏移量(byteOffset)处储存一个16-bit数(无符号短整型). DataView.prototype.setInt32() 从DataView起始位置以byte为计数的指定偏移量(byteOffset)处储存一个32-bit数(长整型). DataView.prototype.setUint32() 从DataView起始位置以byte为计数的指定偏移量(byteOffset)处储存一个32-bit数(无符号长整型). DataView.prototype.setFloat32() 从DataView起始位置以byte为计数的指定偏移量(byteOffset)处储存一个32-bit数(浮点型). DataView.prototype.setFloat64() 从DataView起始位置以byte为计数的指定偏移量(byteOffset)处储存一个64-bit数(双精度浮点型).
- 用法
let buffer = new ArrayBuffer(10),
//使用DataView创建视图的实例
view = new DataView(buffer);
view.setInt8(0,5);
view.setInt8(1,-1);
console.log(view.getInt8(0)); //5
console.log(view.getInt8(1)); //-1
//也可以用getInt16的字节调用,这样使用的话,两个8比特的字符就会合并成一个16bit字符,于是得到的值就是这个了
console.log(view.getInt16()) //1535
定型数组—特殊的视图类型
上面的缓冲区类型视图是可以随意更变的,但是我们只希望处理一种数据类型,这让我们很容易选择和判断
let buffer = new ArrayBuffer(10),
//使用DataView创建视图的实例
view1 = new Int8Array(buffer),
view2 = new Int8Array(buffer,5,2); // 第一个参数是创建的缓冲区,第二个是操作缓冲区的起始位置,第三个参数是缓冲区的长度
console.log(view1.buffer === buffer); //true
console.log(view2.buffer === buffer); //true
console.log(view1.byteOffset); //0
console.log(view2.byteOffset); //5
console.log(view1.byteLength); //10
console.log(view2.byteLength); //2
不用数组缓冲区创建数组,利用定型数组的创建
let ints = new Int16Array(2),
floats = new Float32Array(5);
console.log(ints.byteLength); //4
console.log(ints.length); //2
console.log(floats.byteLength); //20
console.log(floats.length); //5
不给定型参数传值,则不能使用缓存区,因为它的容量默认为0
第三种创建定型数组的方法是调用构造函数时,将以下任一对象作为唯一的参数传入
1、一个定型数组
该数组中的每个元素会作为新的元素被复制到新的定型数组中。例如,如果将一个int8数组传入到Int16Array构造函数中,int8的值会被复制到一个新的int16数组中,新的定型数组使用新的数组缓冲区
2、一个可迭代对象
对象的迭代器会被调用,通过检索所有条目来选取插入到定型数组的元素,如果所有元素都是不适用于该视图类型的无效类型,构造函数将会抛出一个错误
3、一个数组
数组中的元素会被复制到一个新的定型数组中,如果所有元素都是不适用于该视图类型的无效类型,构造函数将会抛出一个错误
4、一个类数组对象
与传入数组的行为一致
let int1 = new Int16Array([15,25]),
int2 = new Int32Array(int1);
console.log(int1.buffer === int2.buffer); //4
console.log(int1.byteLength); //15
console.log(int1[0]) //25
console.log(int1[1]) //8
console.log(int2.byteLength) //2
console.log(int2.length) //15
console.log(int2[0]); //15
console.log(int2[1]); //25
元素大小
每种定型数组由多个元素组成,元素大小,元素大小指的每个元素表示的字节数,该值存储在每个构造函数和每个实例的BYTES_PRE_ELEMENT属性
console.log(UInt8Array.BYTES_PRE_ELEMENT);
console.log(UInt16Array.BYTES_PRE_ELEMENT);
let ints = new Int8Array(5);
console.log(ints.BYTES_PER_ELEMENT);
定型数组也适用于数组的通用方法,但也有区别
//原型不同
let ints = new Int16Array([20,50]);
console.log(ints instanceof Array); //false
console.log(Array.isArray(ints)); //false
//行为差异,数组的元素尺寸大小一致,且不能被扩展
let ints = new Int16Array([25,50]);
console.log(ints.length); //2
console.log(ints[0]); //25
console.log(ints[1]); //50
ints[2] = 5;
console.log(ints.length); //2
console.log(ints[0]); //25
//0被用于代替所有非法值
let ints = new Int16Array(['hi']);
console.log(ints.length); //1
console.log(ints[0]); //0
缺失的方法
- concat()
- shift()
- pop()
- splice()
- push()
- unshift()
附加方法
- set():将其他数组复制到已有的定型数组
- subarray():提取已有定型数组的一部分作为新的定型数组
set() 一个是数组(定型数组或普通数组),一个是可选的偏移量,表示开始插入数据的位置,
let ints = new Int16Array(4);
ints.set([25,50]);
ints.set([125,50],1);
console.log(ints.toString()); //25,50,75,0
subArray()一个是可选的开始位置,一个是可选的结束
let ints = new Int16Array([25,50,75,100]),
subint1 = ints.subarray(),
subint2 = ints.subarray(2),
subint3 = ints.subarray(1,3);
console.log(subint1.toString());//25,50,75,100
console.log(subint2.toString());//75,100
console.log(subint3.toString());//75,100
如果觉得我的文章还可以,点个赞或者关注一下下,还有精彩的故事等着你哦,还可以云撸猫