复习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 声明的常量不属于顶层对象window
  • const 声明的常量不允许重复声明
  • 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:没有返回值,只是针对每个元素调用func
  • map:返回新的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

深拷贝就是对目标的完全拷贝,不像浅拷贝那样只是复制了一层引用,就连值也都复制了。

目前实现深拷贝的方法不多,主要是两种:

  1. 利用 JSON 对象中的 parse 和 stringify
  2. 利用递归来实现每一层都重新创建对象并赋值
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: ƒ}




posted @ 2023-07-01 14:37  小风车吱呀转  阅读(27)  评论(0编辑  收藏  举报