一、 数组的扩展
1. 扩展运算符
- 主要用于函数调用。当用扩展运算符给push方法传参时可实现分步push。具体见例1
- 如果扩展运算符后面是一个空数组则不会有什么效果
- 扩展运算符后面可以放置表达式
- 扩展运算符只有放在函数调用的圆括号里才不会报错
- 在es5中会用apply方法去将数组分解成一个个元素赋值给函数的形参,在es6中可直接使用扩展运算符
- ES5通过
Array.prototype.push.apply(arr1,arr2)
将arr2数组添加到arr1数组上,而ES6可直接通过扩展运算符添加
let [a,...b] = [1,2,3,4];
a//1
b//[2,3,4]
let a = [1,2,3,4,4];
console.log(...a);//12344
//例1
let a = [1,2,3];
a.push([23,4]);//返回结果为数组长度,4,a为[1,2,3,[23,4]]
a.push(...[23,4]);//数组长度为5,a为[1,2,3,23,4]
//放置表达式
const arr = [
...(x > 0 ? ['a'] : []),
'b',
];
//空数组
[...[],1]//[1]
console.log((...[1,2]));//Uncaught SyntaxError: Unexpected number
console.log(...[1,2]);//1,2 扩展运算符放在函数调用的圆括号里,故不报错
//针对5的练习
//es5写法
function f(x,y,z){
console.log(x,y,z)
}
var args = [1,2,3];
f.apply(null,args);1,2,3
//es6写法
f(...args);//1,2,3
//针对6的练习
//es5
var arr1 = [1,2,3];
var arr2 = [2,3,4];
Array.prototype.push.apply(arr1,arr2);//[1,2,3,2,3,4]
//es6
arr1.push(...arr2);//[1,2,3,2,3,4]
扩展运算符的应用
① 复制数组
- 在es6之前,直接将数组的名字赋值给一个新的数组只会赋值原数组的指针,并不是真正的拷贝,故需要通过
旧数组.concat()
赋值给新数组才可得到真正的拷贝数组。- 在es6中,可直接通过扩展运算符的方法实现数组的拷贝
//es5
const a1=[1,2];
const a2 = a1;
a2[0]=2;
//a1:[2,2];
//es5正确克隆
const a1 = [1,2];
const a2 = a1.concat();
a2[0] = 3;
//a1:[1,2]
//es6
const a1 = [1,2];
const a2 = [...a1];//[1,2]
② 合并数组
- es5合并数组需要用到concat()方法,es6只需将不同数组写成[…arr1,…arr2,…arr3]的形式即可合并数组
- 注意:1中两个方法实现的都是浅拷贝,如果修改了原数组或者新数组的成员,会同步反应到新数组(注意:这里修改的不是引用,而是值。若修改了引用,只会改变自身,不会改变其他数组)
//es5
const arr1 = [1,2];
const arr2 = [3,4];
const arr3 = [5,6];
arr1.concat(arr2,arr3);//[1,2,3,4,5,6]
//es6
[...arr1,...arr2,...arr3];//[1,2,3,4,5,6]
③ 可以与解构赋值结合
注意:如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错
const [first,...rest] = [1,2,3,4];
first;//1
rest//[2,3,4]
const [first,...rest]= [];
first;//undefined
rest//[]
const [first,...rest] = ['foo'];
first//"foo"
rest//[]
④ 扩展运算符可以将字符串转成真正的数组
[...'hello'];//["h","e","l","l","o"]
⑤ 任何定义了遍历器(Iterator)接口的对象都可以用扩展运算符转为真正的数组。
//nodeList 定义了遍历器
let nodeList = document.querySelector("div");
let array = [...nodeList];//array为对象数组,nodeList不是真正的数组
//obj未定义遍历器
let obj = {
0:1,
1:2,
2:3,
length:3
}
console.log([...obj]);
2. 数组新增方法
① 将类数组转换成数组:Array.from(类数组)
类数组的特征:有
length
属性,每个元素有其对应的索引
let obj = {
0:1,
1:2,
2:3,
length:3
}
Array.from(obj);//[1,2,3]
//非类数组
let obj = {
a:1,
b:2,
c:3,
length:3
}
Array.from(obj);//[undefined,undefined,undefined]
let obj = {
a:1,
b:2,
c:3
}
Array.from(obj);//[]
② 将值转化成数组:Array.of(temp1,temp2,temp3…)
console.log(Array.of(1,2,3,4));//[1,2,3,4]
③ 遍历数组返回第一个符合回调函数中条件的值:对象.find(fn)
- 若没有找到满足条件的元素则返回undefined
- 当找到符合条件的值后不再判断数组中的其他元素
- find方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组
- 该方法可接受第二个参数,用来绑定回调函数的this对象
- 该方法可以发现NaN,弥补了indexof方法的不足
let a = [1,2,3,4];
console.log(a.find(()=>a>2));//返回3,找到第一个大于2的值并返回
[NaN].indexOf(NaN);//-1
console.log([NaN].find(function (n) {
return Object.is(NaN, n);
}));//NaN
④ 遍历数组返回第一个符合回调函数中条件的值的索引:对象.findIndex(fn)
- 当找到符合条件的值后不再判断数组中的其他元素
- 当没有找到满足条件的元素则返回-1
- 该方法可接受第二个参数,用来绑定回调函数的this对象
let obj = {
a:1,
b:2,
v:3
}
console.log(a.findIndex(function (n){
console.log(this);//this为Obj
if(n>2){
return n;
}
},obj))
[NaN].indexOf(NaN);//-1
console.log([NaN].findIndex(function (n) {
return Object.is(NaN, n);
}));//0
⑤ 用参数值填充数组:数组对象.fill(待填充值)
- 若只有一个参数,该参数将数组中所有元素都替换
- 若该方法有三个参数,则参数依次为:被替换的值,起始位置索引,终止位置索引。(替换的最后一个索引为终止位置索引-1)
- 注意:如果填充的类型为对象,那么被赋值的是同一个内存地址的对象,故当任意一个元素中的值改变时,其余元素的值都改变
let a = [1,2,3,4];
a.fill(7);//a=[7,7,7]
let a = [1,2,3,5,6,7];
a.fill("a",0,4);
console.log(a);//["a","a","a","a",6,7]
let arr = new Array(3).fill({name:"Mike"});
arr[0].name = "Ben";
arr;//[{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]
对象的扩展
1. 对象属性和方法的简写
//属性的简写
let foo = "你好";
let obj = {
foo,//相当于foo:"你好"
hello:function(){
}
}
const foo="bar";
const baz = foo;
baz;//{foo:bar}
//等同于
const baz={foo:"bar"}
function f(x, y) {
return {x, y};
}
// 等同于
function f(x, y) {
return {x: x, y: y};
}
f(1, 2) // Object {x: 1, y: 2}
//方法的简写
const o ={
method(){
return "hello";
}
//相当于
method:functuon(){
return "hello"
}
}
o.method();//hello
//对象的属性和方法简写
let birth = '2000/01/01';
const Person = {
name: '张三',
//等同于birth: birth
birth,
// 等同于hello: function ()...
hello() { console.log('我的名字是', this.name); }
};
2. 属性的取值器getter和赋值器setter(有时间可深度研究)
- 属性的赋值器(setter)和取值器(getter)也是采用属性和方法简写的方式
- 当给属性赋值或者获取属性值时会调用与该属性名称相同的方法,但是在方法中不能设置与属性名相同的属性,否则会持续执行get/set方法。但这种方法会给对象添加一个新的属性,赋值器和取值器方法名称中的属性不会显示出现在最终对象中。(具体看下例)
- 系统的取值器和赋值器请参考js源码
//最终cart对象不会显示出现wheels属性
const cart = {
_wheels: 4,
get wheels () {
return this._wheels;
},
set wheels (value) {
if (value < this._wheels) {
throw new Error('数值太小了!');
}
this._wheels = value;
}
}
cart.wheels = 5;//会调用set wheels方法,如果set后面的名称和属性名不相同,则不会调用
console.log(cart.wheels);//会调用get wheels方法,如果get后面的名称和属性名不相同,则不会调用
wheels对象并不显示展示:
//另一种设置赋值器和取值器的方法
let obj = {
_num: 0,
}
Object.defineProperty(obj, "num", {
get: function () {
//返回值
console.log("我在返回值");
return this._num;
},
set: function (val) {
console.log("设置值");
try {
if (val) {
this._num = val;
}
else {
throw new Error("val is no value");
}
}
catch (err) {
console.log(err);
}
}
})
obj.num=2;//打印:设置值
console.log(obj.num);//打印:我在返回值
obj.age = 19;
console.log(obj);//{_num:2,age:19}
3. super关键字
- super指向当前对象的原型对象(可以理解为后端中的父类)
- super关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。目前,只有对象方法的简写法可以让 JavaScript 引擎确认,定义的是对象的方法。故只能在对象方法的简写中用super
Object.setPrototypeOf(son,father)
方法代表:设置father对象是son对象的原型对象,son对象可以调用father对象的方法以及获取到其的属性。- 若用super调用的方法中用到this,该this依旧指向当前对象,而不是原型对象。
- JavaScript 引擎内部,super.foo等同于
Object.getPrototypeOf(this).foo(属性)
或Object.getPrototypeOf(this).foo.call(this)(方法)
。
const father = {
a:1,
b:4,
getA(){
console.log(this.a);
}
}
const son = {
a:2,
getA(){
super.getA();
}
}
Object.setPrototypeOf(son,father);
son.getA();//输出2
console.log(son.b);//4
console.log(son);
输出的son对象:
//错误的super使用
// 报错
const obj = {
//将得到的值赋给了foo属性
foo: super.foo
}
// 报错
const obj = {
//此处的foo不被认作对象方法,它被认作对象的属性
foo: () => super.foo
}
// 报错
const obj = {
//此处的foo不被认作对象方法,它被认作对象的属性
foo: function () {
return super.foo
}
}
4. 对象扩展符
① 解构赋值
- 对象的解构赋值是从一个对象根据键值取值,并将尚未读取的属性分配到指定的对象上面,所有的键和它们的值都会被拷贝到新对象上(对象的解构赋值不要求左右对象的键顺序一样)
- 由于解构赋值要求右边必须是一个对象,故如果等号右边是null或undefined,会报错,因为它们无法转成对象
- 解构赋值必须是最后一个参数,否则会报错。
- 解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数)、那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。当修改原对象的某一个键对应的值时,新对象中键对应的值也会改变。同理,修改新对象,原对象也会改变
- 扩展运算符的解构赋值不能复制继承自原型对象的属性。但单纯的解构赋值可以取到继承的属性
let {c,...b} = {a:1,b:2,c:4};
c//4
b://{a:1,b:2}
let obj = {a:{b:1}};
let {...x} = obj;
obj.a.b = 2;
x.a.b//2
let o1 = { a: 1 };
let o2 = { b: 2 };
o2.__proto__ = o1;
let { ...o3 } = o2;
o3 // { b: 2 }
o3.a // undefined
//注意点5的易错
const o = Object.create({ x: 1, y: 2 });//o的原型对象为{x:1,y:2}
o.z = 3;
let { x, ...newObj } = o;//x为单纯的解构赋值,故可以取到,而y是扩展运算符的解构赋值,故不能取到y,而z为o对象本身的属性,故可以取到
let { y, z } = newObj;
x // 1
y // undefined
z // 3