复习ES(6-11)语法之ES6上篇
目录
ES6
新的声明方式:let
不属于顶层对象window
var a = 2;
console.log(a); // 2
console.log(window.a); // 2
let b = 5;
console.log(b); // 5
console.log(window.b); //undefined
不允许重复声明
var a = 5;
var a = 6;
console.log(a); // a会从5覆盖成6
let b = 3;
let b = 4;
console.log(b); // 报错:b已声明,无法重新声明
不存在变量提升
console.log(a); //undefined
var a = 5;
//相当于
var a;
console.log(a);
a = 5;
console.log(b); //报错:在b初始化前无法访问“b”
let b = 4;
暂时性死区
ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
var a = 5;
if (true) {
a = 6; //没有报错
var a;
}
var b = 3;
if (true) {
b = 6; //报错:在b初始化前无法访问“b”
let b;
}
块级作用域
在ES5中,作用域只有全局作用域和函数作用域,这就导致出现很多不合理的场景
①用来计数的循环变量泄露为全局变量
for (var i = 0; i < 3; i++) {
console.log("循环内:" + i); // 输出 循环内:[0-2]
}
console.log("循环外:" + i); // 输出 循环外:3
②内层变量可能会覆盖外层变量
var b = 1;
fn();
function fn() {
console.log("b:" + b);
if (true) {
var b = 2;
console.log("内层b:" + b);
}
}
在ES6中新增了块级作用域的概念,使用{}扩起来的区域叫做块级作用域。在块内使用let声明的变量,只会在当前的块内有效。
①使用块级作用域(let定义的变量属于块级作用域) 防止全局变量污染
var b = 1; //定义全局变量
fn();
function fn() {
console.log("b:" + b); // 1
if (true) {
let b = 2;
console.log("内层b:" + b); //内层b:2
}
}
②用来定义计数的循环变量,防止泄露为全局变量
for (let i = 0; i < 3; i++) {
console.log("循环内:" + i); // 输出 循环内:[0-2]
}
console.log("循环外:" + i); // 输出 i is not undefined
新的声明方式:const
const常用来声明不会变化或者不允许修改的常量数据。
const
一旦声明变量,就必须立即初始化,不能留到以后赋值
const b; //报错 必须初始化const声明
b = 2
const
声明的常量不允许修改,但如果是对象,可以修改对象里面的内容。
const PI = 3.1415926;
PI = 22; // Assignment to constant variable.(报错:给常量赋值)
const user = {
name: "zzz",
age: 18,
};
user.name = "小风车";
console.log(user); //{name: '小风车', age: 18}
const
定义中的不变指的是指向对象的指针不变。原始值
是按值访问的,引用值
是由多个值构成的对象,修改对象中的属性并不会让指向对象的指针发生改变,所以可以改变const定义对象的属性。
const
声明的常量不属于顶层对象windowconst
声明的常量不允许重复声明const
声明的常量不存在变量提升const
声明的常量也拥有暂时性死区const
只在声明所在的块级作用域内有效
解构赋值
解构赋值:按照一定模式,从数组和对象中提取值,对变量进行赋值。
数组解构
//没有使用数组解构的情况下,从数组中提取值赋值到a,b,c三个变量上,需要写这么多行代码
let arr = [1, 2, 3];
let a = arr[0];
let b = arr[1];
let c = arr[2];
console.log(a, b, c); // 1,2,3
//使用数组解构
let arr = [1, 2, 3];
let [a, b, c] = arr;
console.log(a, b, c); // a,b,c
只要等号左右两边结构一致,就能成功解构赋值
let [a, b, [c, d]] = [1, 2, [3, 4]];
console.log(a, b, c, d); // 1,2,3,4
let [a,b,[c]] = [1,2,[3,4]]
console.log(a, b, c); // 1,2,3
let [a,b,c] = [1,2,[3,4]]
console.log(a, b, c); // 1,2,[3,4]
解构赋值
是一种惰性赋值
,如果数组比左边的变量列表短,这里也不会出现报错。缺少的值被认为是 undefined
;解构赋值还可以使用默认值,这样就避免了不存在的属性返回undefined
的问题,如果右边有对应的值则会取代默认值。
let [a, b, c, d] = [1, 2, [3, 4]];
console.log(a, b, c, d); // 1,2,[3,4],undefined
let [a, b, c, d = 5] = [1, 2, [3, 4]];
console.log(a, b, c, d); // 1,2,[3,4],5
let [a, b, c, d = 5] = [1, 2, [3, 4],6];
console.log(a, b, c, d); // 1,2,[3,4],6
对象解构
对象解构时对变量的顺序并不重要,因为等号左边指定了属性和变量之间的映射关系。还可以使用“什么值:赋值给谁”的形式来对变量进行重命名。
let user = {
name: "zzz",
age: 18,
};
let { age, name } = user;
let { age: uage, name: uname } = user;
console.log(name, age); //zzz 18
console.log(uname, uage);
对于可能缺失的属性,可以使用 "="
设置默认值
let user = {
name: "zzz",
};
let { name, age = 18 } = user;
let { name: uname, age: uage = 17 } = user;
console.log(name, age); //zzz 18
console.log(uname, uage); // zzz 17
字符串解构
- 字符串的解构赋值,将字符串看作一个类似于数组的对象。
let [a, b, c] = "apple";
console.log(a, b, c); // a,p,p
- 类似数组的对象都有一个length属性,因此还可以对这个属性进行解构。
let { length: len } = "apple";
console.log(len); //5
数组的各种遍历方式
ES5中数组遍历方式
for
循环,支持使用break
停止循环,用continue
跳过本次循环forEach
:没有返回值,只是针对每个元素调用funcmap
:返回新的Array
,每个元素为调用func的结果filter
:返回符合func条件的元素数组some
:返回boolean,判断是否有元素是否符合func条件every
:返回boolean,判断每个元素是否符合func条件reduce
:接收一个函数作为累加器- for in ???:有问题,会遍历出数组原型上的方法
let arr = [1, 2, 3];
// for
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
// forEach
arr.forEach((elem, index, array) => {
console.log(elem, index);
});
// map
let res = arr.map((value) => {
value += 1;
return value;
});
console.log(res); //[ 2, 3, 4 ]
//some
let result = arr.some((value) => {
return value == 2;
});
console.log(result); // true
//every
let everyResult = arr.every((value) => {
return value == 2;
});
console.log(everyResult); // false
// reduce
let sum = arr.reduce((prev, cur) => {
return prev + cur;
}, 0);
console.log(sum); // 6
// 利用reduce求出数组中的最大值
let max = arr.reduce((prev, cur) => {
return Math.max(prev, cur);
});
console.log(max); // 3
// 利用reduce提取值到新数组
let new_arr = arr.reduce((prev, cur) => {
prev.indexOf(cur) == -1 && prev.push(cur);
return prev;
}, []);
console.log(new_arr); // [1,2,3]
// for in
Array.prototype.foo = function () {
console.log("foo");
};
for (let index in arr) {
console.log(index, arr[index]);
}
ES6中数组遍历方式
- find:返回第一个通过测试的元素
- findIndex:返回的值为该通过第一个元素的索引
- for of
- values()
- keys()
- entries()
//for of
for (let item of arr) {
console.log(item);
}
// for of + values()
for (let item of arr.values()) {
console.log(item);
}
// for of + keys()
for (let index of arr.keys()) {
console.log(index);
}
// for of + entries()
for (let [index, item] of arr.entries()) {
console.log(index, item);
}
数组的扩展
- 类数组 / 伪数组
类数组
有以下特点:
①拥有length属性,可以获取长度
②拥有角标索引,可以通过索引获取值
③不可以使用数组的内置方法,因为伪数组的原型指向的是Object对象
// DOM
let divs = document.getElementsByTagName("div"); // HTMLCollection[]
console.log(divs instanceof Array); // false
function foo() {
console.log(arguments instanceof Array); // false
}
foo(1, "zzz", true);
let arrayLike = {
0: "es6",
1: "es7",
2: "es8",
length: 3,
};
// 利用Array的原型对象的slice方法,配合call()方法修改slice中this指向
let newArr = Array.prototype.slice.call(arrayLike);
newArr.push("es9");
console.log(newArr);
// 伪数组可以通过Array.from转化为真正意义上的数组
let arr = Array.from(arrayLike);
arr.push("es9");
console.log(arr);
Array.from()
:将一个类数组对象
或者可遍历对象
转换成一个真正的数组
Array.of()
:创建一个可变数量参数
的新数组,而不考虑参数的类型和数量。
let arr1 = new Array(1, 2);
console.log(arr1); // [ 1, 2 ]
let arr2 = new Array(3);
console.log(arr2); // [ <3 empty items> ]
let _arr1 = Array.of(1, 2);
console.log(arr1); // [ 1, 2 ]
let _arr2 = Array.of(3);
console.log(_arr2); // [ 3 ]
copyWithin()
:用于从数组的指定位置拷贝元素到数组的另一个指定位置中。返回修改后的数组(即直接修改原数组
),不会改变数组的长度
let arr = [1, 2, 3, 4, 5];
arr.copyWithin(1, 3);
console.log(arr); // [ 1, 4, 5, 4, 5 ]
fill()
:用一个固定值填充一个数组中,从起始索引到终止索引内的全部元素,不包括终止索引,返回被修改后的数组。
let arr3 = [1, 2, 3, 4, 5];
arr.fill(0, 1, 3);
console.log(arr);
let arr4 = new Array(6).fill(1);
console.log(arr4); //[ 1, 1, 1, 1, 1, 1 ]
includes()
:用来判断一个数组是否包含指定的值,包含返回ture,否则为false;indexOf
则是返回索引值,includes可以判断有NaN
的元素,indexOf
不能
let arr = ["a", "b"];
console.log(arr.includes("a")); // true
console.log(arr.indexOf("a")); // 0
arr = ["a", "b", NaN];
console.log(arr.includes(NaN)); // true
console.log(arr.indexOf(NaN)); // -1
函数的参数
- 参数的默认值:函数的参数默认是undefined。然而,在某些情况下可能需要设置一个不同的默认值
function multiply(a, b = 1) {
return a * b;
}
multiply(5, 2); // 10
multiply(5); // 5
- 与解构赋值结合
function foo({ name, age }) {
console.log(name, age);
}
foo({ name: "zzz", age: 18 }); // zzz 18
foo({}); // undefined undefined
foo(); // 报错
- length属性:
length
是函数对象的一个属性值,指该函数有多少个必须要传入的参数
,即形参的个数。形参的数量不包括剩余参数个数,仅包括第一个具有默认值之前
的参数个数
function fn1(name) {}
function fn2(name = "zzz") {}
function fn3(name, age = 18) {}
function fn4(name, age = 18, gender) {}
function fn5(name = "zzz", age, gender) {}
console.log(fn1.length); // 1
console.log(fn2.length); // 0
console.log(fn3.length); // 1
console.log(fn4.length); // 1
console.log(fn5.length); // 0
- 函数的name属性:所有的函数都有一个
name
属性,该属性保存的是该函数名称的字符串。
function foo() {}
console.log(foo.name); // foo
console.log(new Function().name); // anonymous
console.log(foo.bind({}).name); // bound foo
console.log(function () {}.bind({}).name); // bound
扩展运算符与rest参数
用 ...
表示
- 扩展运算符:把数组或者类数组展开成用逗号隔开的值
// 扩展运算符
function foo(a, b, c) {
console.log(a, b, c);
}
let arr = [1, 2, 3];
foo(...arr);
// 使用扩展运算符合并2数组
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
// Array.prototype.push.apply(arr1, arr2); //ES5写法
arr1.push(...arr2);
console.log(arr1);
let str = "apple";
let strArr = [...str];
console.log(strArr); // [ 'a', 'p', 'p', 'l', 'e' ]
- rest参数:把逗号隔开的值组合成一个数组
// 不定参数求和问题
function foo(x, y, z) {
let sum = 0;
// Array.prototype.forEach.call(arguments, function (item) {
// sum += item;
// }); ①
Array.from(arguments).forEach((item) => {
sum += item;
});
return sum;
}
console.log(foo(1, 2)); // 3
console.log(foo(1, 2, 3)); //6
如果使用rest参数可以这样写:
function foo(...args) {
return args.reduce((prev, cur) => prev + cur);
}
console.log(foo(1, 2)); // 3
console.log(foo(1, 2, 3)); //6
其他使用例子:
function bar(x, ...args) {
console.log(...args);
}
foo(1, 2, 3); // [2,3]
foo(1, 2, 3, 4); // [2,3,4]
let [x, ...y] = [1, 2, 3];
console.log(x); // 1
console.log(y); // [2,3]
箭头函数
- this指向定义时所在的对象,而不是调用时所在的对象
普通函数的this指向的是调用时所在的对象,所以会发生以下情况
let oBtn = document.querySelector("#btn");
oBtn.addEventListener("click", function () {
console.log(this); // 打印的是button本身
// 点击时需要延迟一秒才打印
setTimeout(function () {
console.log(this); // 此时this指向的是window对象,调用setTimeout是window对象
}, 1000);
});
//可以使用bind改变this指向
setTimeout(
function () {
console.log(this);
}.bind(this),
1000
);
//使用箭头函数
setTimeout(() => {
console.log(this);
}, 1000);
- 不可以当作构造函数(因为箭头函数没有属于自己的this关键字,有都是来自于父级作用域。)
- 不可以使用arguments对象(箭头函数里面没有arguments,可以使用
…reset
,接收过来就是数组类型,接收的是形参之外的所有的实参)
对象的扩展
- 属性简洁表示法
let name = "zzz";
let age = 11;
let obj = {
name,
age,
};
console.log(obj);
- 属性名表达式
let name = "zzz";
let age = 11;
let s = "school";
let obj = {
name,
age,
[s]: "小学",
};
console.log(obj);
- Object.is():两个值是否为相同值。如果以下其中一项成立,则两个值相同:
- 都是
undefined
- 都是
null
- 都是
true
或者都是false
- 都是长度相同、字符相同、顺序相同的字符串
- 都是相同的对象(意味着两个值都引用了内存中的同一对象)
- 都是 BigInt 且具有相同的数值
- 都是 symbol 且引用相同的 symbol 值
- 都是数字且
- 都是
+0
- 都是
-0
- 都是
NaN
- 都有相同的值,非零且都不是
NaN
- 都是
- 都是
Object.is(25, 25); // true
Object.is("foo", "foo"); // true
Object.is("foo", "bar"); // false
Object.is(null, null); // true
Object.is(undefined, undefined); // true
Object.is(window, window); // true
Object.is([], []); // false
const foo = { a: 1 };
const bar = { a: 1 };
const sameFoo = foo;
Object.is(foo, foo); // true
Object.is(foo, bar); // false
Object.is(foo, sameFoo); // true
- 扩展运算符与Object.assign()
// 实现对象的复制
let x = {
a: 3,
b: 4,
};
let y = { ...x };
let z = {};
Object.assign(z, x);
console.log(y, z); // { a: 3, b: 4 } { a: 3, b: 4 }
- in:可以判断当前对象是否拥有某个属性
console.log("aa" in x);
- 对象的遍历方式
let obj = {
name: "zzz",
age: 11,
school: "小学",
};
for (let key in obj) {
console.log(key, obj[key]);
}
Object.keys(obj).forEach((key) => {
console.log(key, obj[key]);
});
Object.getOwnPropertyNames(obj).forEach((key) => {
console.log(key, obj[key]);
});
Reflect.ownKeys(obj).forEach((key) => {
console.log(key, obj[key]);
});
深拷贝与浅拷贝
对象的浅拷贝
就只是复制对象的引用,如果拷贝后的对象发生变化,原对象也会发生变化。只有深拷贝
才是真正地对对象的拷贝。
//浅拷贝
let obj1 = {
name: "zzz",
age: 11,
};
let obj2 = obj1;
obj1.age = 18;
console.log(obj1.age); // 18
console.log(obj2.age); // 18
深拷贝就是对目标的完全拷贝,不像浅拷贝那样只是复制了一层引用,就连值也都复制了。
目前实现深拷贝的方法不多,主要是两种:
- 利用
JSON
对象中的parse
和stringify
- 利用递归来实现每一层都重新创建对象并赋值
let obj1 = {
name: "zzz",
age: 11,
};
let str = JSON.stringify(obj1);
let obj2 = JSON.parse(str);
obj1.age = 18;
console.log(obj2.age); // 11
// 判断是数组还是对象
let checkType = (data) => {
return Object.prototype.toString.call(data).slice(8, -1);
};
// 深拷贝
let deepClone = (target) => {
let targetType = checkType(target);
let result;
if (targetType === "Object") { //如果是对象
result = {};
} else if (targetType === "Array") { //如果是数组
result = [];
} else {
return target;
}
for (let i in target) {
let value = target[i];
let valueType = checkType(value);
if (valueType === "Object" || valueType === "Array") {
result[i] = deepClone(value);
} else {
result[i] = value;
}
}
return result;
};
// const originObj = { a: "a", b: "b", c: [1, 2, 3], d: { dd: "dd" } };
// const cloneObj = deepClone(originObj);
// console.log(cloneObj === originObj); // false
// cloneObj.a = "aa";
// cloneObj.c = [1, 1, 1];
// cloneObj.d.dd = "doubled";
// console.log(cloneObj); // {a:'aa',b:'b',c:[1,1,1],d:{dd:'doubled'}};
// console.log(originObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
const originObj = {
name: "zzz",
sayHello: function () {
console.log("Hello World");
},
};
console.log(originObj); // {name: "zzz", sayHello: ƒ}
const cloneObj = deepClone(originObj);
console.log(cloneObj); // {name: "zzz", sayHello: ƒ}