解构
1、什么是解构
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring),解构的本质属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。如果解构不成功,变量的值就等于undefined。
2、数组解构
- 拓展运算符
拓展运算符使用三个点(…)表示。好比将一个数组转为用逗号分隔的参数序列。 例如:
let arr1 = [1,2,3];
let arr2 = [4,5,6];
let arr = [...arr1,...arr2];
console.log(arr);
let temp = [..."hello"];
console.log(temp);
let a = 1;
let b = 2;
let c = 3;
ES6 允许写成下面这样。等号左边的变量放到中括号内部,匹配右侧数组中的元素。
let [a, b, c] = [1, 2, 3];
上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值。 本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。 看如下例子:
let [foo, [[bar], baz]] = [1, [[2], 3]];
console.log(foo);
console.log(bar);
console.log(baz);
let [ , , third] = ["foo", "bar", "baz"];
console.log(third);
let [x, , y] = [1, 2, 3];
x
y
let [head, ...tail] = [1, 2, 3, 4];
head
tail
let [x, y, ...z] = ['a'];
x
y
z
let [bar, foo] = [];
let [bar, foo] = [1];
不完全解构
不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。
let [x, y] = [1, 2, 3];
x
y
let [a, [b], d] = [1, [2, 3], 4];
a
b
d
如果等号的右边不是数组(或者严格地说,不是可遍历的结构)那么将会报错.
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};
let [a] = 'hello';
a
上面的六个表达式都会报错,因为等号右边的值,要么转为对象以后不具备 Iterator 接口(前五个表达式),要么本身就不具备 Iterator 接口(最后一个表达式)。
事实上,只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。原生具备 Iterator 接口的数据结构:Array、Map、Set、String、TypedArray、arguments、NodeList 等。
集合结构 使用... 扩展运算符接收剩余的数据
let old = [1, 2, 3, 4]
let [...arr] = old;
console.log(old);
console.log(arr)
console.log(arr === old);
待补充解释。。。
默认值 解构赋值允许指定默认值。注意,ES6 内部使用严格相等运算符(=== ),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined ,默认值才会生效。
let [x, y = 'b'] = ['a'];
function f() { console.log('aaa'); }
let [x = f()] = [1];
let [y = f] = [];
let [z = f()] = [];
3、对象解构
对象解构指的是将等号左边的变量放到大括号内部,匹配右侧数组中的元素。 对象的属性没有次序,变量必须与属性同名,才能取到正确的值。如下:
let { foo, bar } = { foo: "aaa", bar: "bbb" };
重命名结构 如果变量名与属性名不一致,必须写成下面这样进行重命名。
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
这实际上说明,对象的解构赋值是下面形式的简写。
let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };
嵌套解构
let obj = { p: [ "Hello", { y: "World" } ] };
let { p: [x, { y }] } = obj;
默认值
默认值生效的条件是,对象的属性值严格等于undefined
let {x: y = 3} = { x: 1 };
let {x: y = 3} = {};
运算符… **…用到左侧是聚合,…**用到右侧就是展开
let obj = { name: 'zhangsan', age: 12 }
let { ...person } = obj
person.gender = "1";
console.log(person)
console.log(person === obj)
let stu = {
...obj,
gender: '1'
}
console.log(stu)
let {gender="2",...zs} = stu;
console.log(gender,zs)
4、字符串解构
等号左边的变量如果放在中括号内进行的类似于数组解构,从字符串中获取指定字符;如果放在大括号内进行的类似于对象解构,从实例属性获方法中解构。
const [a, b, c, d, e] = 'hello';
let {length : len, toString } = 'hello';
let [...arr] = 'hello';
console.log(arr);
5、其他不咋用解构
数值解构
等号左边的变量放在大括号中进行解构,可以获取到数值包装器构造函数原型中指定的方法。
let { valueOf } = 12;
布尔类型解构
等号左边的变量放在大括号中进行解构,可以获取到布尔包装器构造函数原型中指定的方法。
let { valueOf } = true;
常用扩展
1、对象扩展
ES6中对于Object的拓展主要是静态方法的拓展。
- Object.is()
该方法用来判断两个值是否“相等”,或者说是否为同一个值。 格式:Object.is(value1,value2); 结果:返回一个Bolean类型表示两个参数是否为同一个值。为true则相同,为false表示不同
该方法和"=="有区别: "==“在判断相等前对两边的变量(如果不是同一类型)进行强制转换,而Object.is()方法不会这么做 该方法和”==="也有区别: “===“先比较类型,类型不相同则结果一定不等,如果类型相同,则继续比较值,值相等,则结果相等。并且”===” 将数字 -0 和 +0 视为相等,而将Number.NaN 与NaN视为不相等,而Object.is()方法将这两个均视为不等。
Object.is('foo', 'foo');
Object.is(window, window);
Object.is('foo', 'bar');
Object.is([], []);
let foo = { a: 1 };
let bar = { a: 1 };
Object.is(foo, foo);
Object.is(foo, bar);
Object.is(null, null);
console.log(1 === 1);
console.log(Object.is(1, 1));
console.log(Object.is(1, 2));
console.log(+0 === -0);
console.log(Object.is(+0, -0));
console.log(0 === -0);
console.log(Object.is(0, -0));
console.log(NaN === NaN);
console.log(Object.is(NaN, NaN));
console.log(isNaN(NaN));
console.log(NaN === 0/0);
console.log(Object.is(NaN, 0/0));
简单理解就是Object.is()方法比较值其实就是看两个值的存储位置是不是一样,一样则相等。
- Object.assign()
该方法用于将所有可枚举类型的值从一个或多个源对象分配到目标对象。返回目标对象。 语法:object.assign(target,...sources); target是目标对象,sources源对象,而可以为一个或多个。
如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖。后面的源对象的属性将类似地覆盖前面的源对象的属性,此外需要注意Object.assign 方法只会拷贝源对象自身的并且可枚举的属性到目标对象。看如下例子
const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };
const returnedTarget = Object.assign(target, source);
console.log(target);
console.log(returnedTarget);
补充:该方法使用源对象的[[Get]]和目标对象的[[Set]],所以它会调用相关 getter 和 setter。因此,它分配属性,而不仅仅是复制或定义新的属性。 如果合并源包含getter,这可能使其不适合将新属性合并到原型中。为了将属性定义(包括其可枚举性)复制到原型,应使用Object.getOwnPropertyDescriptor()和Object.defineProperty()。 Object.assign()拷贝的是(可枚举)属性值。假如源值是一个对象的引用,它仅仅会复制其引用值。
- Object.getPrototypeOf()
该方法返回指定对象的原型。 语法:Object.getPrototypeOf(object); object是要返回其原型的对象 返回的是给定对象的原型,如果没有继承属性则返回null
let obj = {};
console.log(Object.getPrototypeOf(obj));
console.log(Object.getPrototypeOf(obj)===Object.prototype);
console.log(Object.getPrototypeOf(obj)===obj.__proto__);
- Object.setPrototypeOf()
该方法方法设置一个指定的对象的原型到另一个对象或 null。 语法:Object.setPrototypeOf(obj,prototype); obj是要设置其原型的对象,protoType是该对象obj的新原型
let dict = Object.setPrototypeOf({}, null);
由于现代 JavaScript 引擎优化属性访问所带来的特性的关系,更改对象的 [[Prototype]]在各个浏览器和 JavaScript 引擎上都是一个很慢的操作。其在更改继承的性能上的影响是微妙而又广泛的,这不仅仅限于 obj.proto = … 语句上的时间花费,而且可能会延伸到任何代码,那些可以访问任何[[Prototype]]已被更改的对象的代码。如果关心性能,应该避免设置一个对象的 [[Prototype]]。相反,应该使用 Object.create()来创建带有想要的[[Prototype]]的新对象。
- Object.keys()
该方法用于获取所有可枚举属性名,并且返回一个有一个给定对象自身可枚举属性组成的数组,数组中属性名的排序和正常循环遍历该对象时返回的顺序一致。 语法:object.keys(obj); obj是要返回其枚举自身属性的对象。
let arr = ['a', 'b', 'c'];
console.log(Object.keys(arr));
let obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.keys(obj));
Object.keys(obj).forEach((item) => {
console.log(item, obj[item]);
})
let anObj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.keys(anObj));
let myObj = Object.create({}, {
getFoo: {
value: function () { return this.foo; }
}
});
myObj.foo = 1;
console.log(Object.keys(myObj));
当然,如果想获取一个对象的所有属性,甚至包括不可枚举的,请使用Object.getOwnPropertyNames()方法。
- Object.value()
该方法可以获取所有可枚举属性的属性值,并返回一个给定对象自身的所有可枚举属性值的数组,值的顺序与使用for…in循环的顺序相同(区别在于for-in循环枚举原型链中的属性)。 语法:Object.values(obj); obj表示可以枚举属性值的对象。
let obj = { foo: 'bar', baz: 42 };
console.log(Object.values(obj));
let obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.values(obj));
let an_obj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.values(an_obj));
let my_obj = Object.create({}, { getFoo: { value: function() { return this.foo; } } });
my_obj.foo = 'bar';
console.log(Object.values(my_obj));
console.log(Object.values('foo'));
- Object.entries
该方法获取所有的可枚举属性名和属性值键值对,返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for…in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性)。 语法:Object.entries(obj); obj 可以返回其可枚举属性的键值对的对象。
const obj = { foo: 'bar', baz: 42 };
console.log(Object.entries(obj));
const obj2 = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.entries(obj));
const anObj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.entries(anObj));
const myObj = Object.create({}, { getFoo: { value() { return this.foo; } } });
myObj.foo = 'bar';
console.log(Object.entries(myObj));
console.log(Object.entries('foo'));
const obj3 = { a: 5, b: 7, c: 9 };
for (const [key, value] of Object.entries(obj)) {
console.log(`${key} ${value}`);
}
Object.entries(obj).forEach(([key, value]) => {
console.log(`${key} ${value}`);
});
2、函数扩展
function log(x, y = 'World') {
console.log(x, y);
}
log(1);
- 参数结构
参数默认值可以与解构赋值的默认值,结合起来使用。
function foo({x, y = 5}) {
console.log(x, y);
}
foo({x:1});
此外,函数参数对象解构、数据结构等。
function test({ name, age = 1, ...obj }) {
console.log(name, age, obj);
}
test({ name: 'zhangsan', age: 12, gender: 1,weight:40 });
- rest参数
ES6 引入 rest 参数(形式为…变量名),用于获取函数的多余参数得到一个数组,这样就不需要使用arguments对象了。
function add(...values) {
console.log(values);
}
add(2, 5, 3)
箭头函数
JavaScript中,经常使用回调函数,箭头函数的出现大大简化了回调函数的写法,当然,除了作为函数参数,箭头函数也可以出现在其他地方。 如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。 语法:(0个或1个或多个参数)=>{代码块部分} 箭头函数里面没有自己的this,而是引用外层作用域中的this,与传统的JavaScript 函数有些不同,具体看下面几点:
- 没有
this 、super 、arguments - 不能通过
new 关键字调用 - 没有原型
prototype - 不可以改变
this 指向 - 不支持重复的命名参数
使用如下:
let f = v => v;
let f = function(v) {
return v;
};
let obj = {
name:"terry",
say1:function(){
console.log(this.name)
},
say2() {
console.log(this.name)
},
say3: () => {
console.log(this)
},
say4() {
return () => {
console.log(this);
}
}
};
obj.say1();
obj.say2();
obj.say3();
obj.say4()();
重点: 如果将普通匿名函数和箭头函数分别定义在setTimeout(function(),时间)中。那么区别在哪里?如下:
var obj = {
x:100,
show(){
setTimeout(){
()=>{console.log(this.x);),
500
);
}
};
使用箭头函数简写时,this调用的对象时obj
var obj = {
x:100,
show(){
setTimeout(){
function(){console.log(this.x);),
500
);
}
};
使用匿名函数时,this调用的对象时全局对象,因为匿名函数是在延时器setTimeout()中,而延时器又不在主线程内,所以是全局对象在调用。
这其实也是作用域链的问题,只不过最顶部的作用域对象不同。
3、数组扩展
数组在静态方法与实例方法中都有所拓展。
- Array.from()
该方法将类似数组或可迭代对象转为数组,从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。 语法:Array.from(arrayLike[, mapFn[, thisArg]]) 参数:
- arrayLike 想要转换成数组的伪数组对象或可迭代对象。
- mapFn 可选 如果指定了该参数,新数组中的每个元素会执行该回调函数。
- thisArg 可选 执行回调函数 mapFn 时 this 对象。
Array.from() 可以通过以下方式来创建数组对象: 伪数组对象(拥有一个 length 属性和若干索引属性的任意对象) 可迭代对象(可以获取对象中的元素,如 Map和 Set 等)
Array.from('foo');
function f() {
return Array.from(arguments);
}
f(1, 2, 3);
- Array.of()
该方法创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型。 语法:Array.of(element0[, element1[, ...[, elementN]]]) 参数: elementN 任意个参数,将按顺序成为返回数组中的元素。
Array.of(7);
Array.of(1, 2, 3);
Array(7);
Array(1, 2, 3);
Array.of(1);
Array.of(1, 2, 3);
Array.of(undefined);
该方法和 Array 构造函数之间的区别在于处理整数参数:Array.of(7) 表示创建一个具有单个元素 7 的数组,而Array(7) 表示创建一个长度为7的空数组(注意:这是指一个有7个空位(empty)的数组,而不是由7个undefined组成的数组)。
-
Array.prototype.find() 该方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。 语法:arr.find(callback,[thisArg]) 参数: callback 在数组每一项上执行的函数,接收3个参数:
- tem 当前遍历到的元素。
- index 可选 当前遍历到的索引。
- array 可选 数组本身。
thisArg 可选 执行回调时用作this 的对象。 -
Array.prototype.findIndex() 该方法找到一个元素在数组中的位置,返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回-1。 语法:arr.findIndex(callback[, thisArg]) 参数: callback 在数组每一项上执行的函数,接收3个参数:
- tem 当前遍历到的元素。
- index 可选 当前遍历到的索引。
- array 可选 数组本身。
thisArg 可选 执行回调时用作this 的对象。
let arr = [10,8,3,2,4,2,5];
let resultNum = arr.find((item, index) => {
return item === 2;
});
console.log(resultNum);
let resultIdx = arr.findIndex((item, index) => {
return item === 2;
});
console.log(resultIdx);
function isPrime(element, index, array) {
let start = 2;
while (start <= Math.sqrt(element)) {
if (element % start++ < 1) {
return false;
}
}
return element > 1;
}
console.log([4, 6, 8, 12].findIndex(isPrime));
console.log([4, 6, 7, 12].findIndex(isPrime));
- Array.prototype.includes()
该方法找到一个元素是否存在于数组中,用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。使用 includes()比较字符串和字符时是区分大小写。 语法:arr.includes(valueToFind,[ fromIndex]) 参数:
- valueToFind 需要查找的元素值。
- fromIndex 可选 从fromIndex 索引处开始查找 valueToFind。如果为负值,则按升序从 array.length + fromIndex 的索引开始搜 (即使从末尾开始往前跳 fromIndex 的绝对值个索引,然后往后搜寻)。默认为 0。如果 fromIndex 大于等于数组的长度,则会返回 false,且该数组不会被搜索。
返回值: 返回一个布尔值 Boolean ,如果在数组中找到了(如果传入了 fromIndex ,表示在 fromIndex 指定的索引范围中找到了)则返回 true 。
[1, 2, 3].includes(2);
[1, 2, 3].includes(4);
[1, 2, 3].includes(3, 3);
[1, 2, 3].includes(3, -1);
[1, 2, NaN].includes(NaN);
- Array.prototype.fill()
该方法用来填充数组,用一个固定值填充一个数组中从起始索引到终止索引内的全部元素。不包括终止索引。 语法: arr.fill(value[, start[, end]]) 参数:
- value 用来填充数组元素的值。
- start 可选 起始索引,默认值为0。
- end 可选 终止索引,默认值为 this.length。
如果 start和end 是个负数, 则开始索引会被自动计算成为 length+start
[1, 2, 3].fill(4);
[1, 2, 3].fill(4, 1);
[1, 2, 3].fill(4, 1, 2);
[1, 2, 3].fill(4, 1, 1);
[1, 2, 3].fill(4, 3, 3);
[1, 2, 3].fill(4, -3, -2);
- Array.prototype.keys()
该方法获取数组key,返回一个包含数组中每个索引键的Array Iterator对象。 语法:arr.keys()
let arr = ["a", , "c"];
let sparseKeys = Object.keys(arr);
let denseKeys = [...arr.keys()];
console.log(sparseKeys);
console.log(denseKeys);
let arr = [2, 3, 4, 5, 6, 2];
let keys = arr.keys();
console.log(keys);
let result;
while (!(result = keys.next()).done) {
console.log(result);
}
for (let key of keys) {
console.log(key);
}
Array.prototype.values() Array.prototype.values()方法获取数组元素,返回一个新的 Array Iterator 对象,该对象包含数组每个索引的值。 语法:arr.values()
let arr = ['w', 'y', 'k', 'o', 'p'];
let eArr = arr.values();
for (let letter of eArr) {
console.log(letter);
}
- Array.prototype.entries()
该方法获取数组中的key、value键值对,返回一个新的Array Iterator对象,该对象包含数组中每个索引的键/值对。 语法:arr.entries() 一个新的 Array 迭代器对象。Array Iterator是对象,它的原型(proto:Array Iterator)上有一个next方法,可用用于遍历迭代器取得原数组的[key,value]。
let arr = ["a", "b", "c"];
let iterator = arr.entries();
console.log(iterator);
for (let letter of iterator) {
console.log(letter);
}
|