js重难点精讲一(ES6一)| 8月更文挑战

浑浑噩噩了五年,结了婚生了孩子,时间不够了才知道好好学习~~~~~~

let和const

  1. 在ES6之前只存在全局作用域和函数作用域

    1. 导致变量提升
    2. 在代码块外仍可以访问
  2. ES新增了块级作用域(变量可执行上下文为一个代码块中即一对花括号中)

    1. 不存在变量提升
    2. 存在暂时性死区
      1. 定义变量之前,该变量不能访问
      2. 所以typeOf也不是绝对安全的(使用var定义使用typeOf绝对安全,可返回undefined)
    3. 不能重复声明
    function foo(){
      let flag = true;
      if(true){
        let flag = false; // 这里可以因为不在同一作用域块
      }
      var flag = true; // Identifier 'flag' has already been declared
    }
    复制代码
    1. 在全局作用域下使用var定义的变量是全局对象(window)的属性,但是使用let定义的变量不再是全局对象的属性
  3. let的特点:

    a. 不会导致for循环索引值泄漏

    var arr = [];
    for(var i=0; i<10; i++){
      arr[i] = function(){
        console.log(i)
      }
    }
    arr[1]() // 10
    // 通过var声明的索引i是一个全局变量,每一次循环,全局变量i都会发生改变。而数组arr所有成员里面的i都指向同一个i,当循环结束后,全局变量i的值已经变为10
    
    
    var arr = [];
    for(let i=0; i<10; i++){
      arr[i] = function(){
        console.log(i)
      }
    }
    arr[1]() // 1
    // 通过let定义的索引值i,只在当前循环内有效,实际上每一轮循环中的i都是一个新的变量,而且最关键的是JavaScript引擎能够记住上一轮循环的值,所以在本轮循环开始时,会基于上一轮循环计算,从而索引i的值会递增。
    复制代码
    • 为什么通过let声明的变量i在循环体外,仍然可以访问呢?这是因为arr数组的每个成员都是一个函数,对变量i的引用构成了一个闭包,所以在循环体外调用函数时仍然可以访问到i
    • for循环还有一个特别之处,声明变量的那部分(小括号内部)是一个父作用域,循环体内部是一个单独的子作用域

    b. 避免变量提升导致的变量被覆盖问题

    var name = 'yangf';
    function foo(){
      console.log(name); // undefind
      if(false){
        var name = 'yangf';
      }
    }
    foo();
    复制代码

    c. 代替立即执行函数IIFE

    • 使用IIFE的目的主要是避免污染当前作用域内的变量,而使用块级作用域则可以直接避免这个问题
    (function(){
      var name = ...
    }())
    // 块级作用域
    {
      let name = ...
    }
    复制代码

    d.const基本和let的特点一致,还有个特别之处是,因为const定义的是常量,所以定义了不能改变

    • 对于基本类型的变量来说,变量就保存着内存地址的值,因此不能直接修改;而对于引用类型的变量来说,变量保存的是一个指向数据内存地址的指针,只要该指针固定不变,我们就可以改变数据本身的值
const person = {
  name: 'yangf'
}
person.age = '18';
console.log(person) // {name: "yangf", age: "18"} 
person = {
  name: 'xiaoliu'
}; // Assignment to constant variable
复制代码

解构赋值

将值从数组或者对象中提取出来,并赋值到不同的变量中。

  1. 数组的解构赋值

数组的解构赋值是基于数组元素的索引

a. 使用的是模式匹配,只要等号两边数组的模式相同,右边数组的值就会相应赋给左边数组的变量

let [num1,num2] = [10,11];
console.log(num1);// 10
console.log(num2);// 11
复制代码

b. 可以只解构出感兴趣的值,对于不感兴趣的值使用逗号作为占位符,而不指定变量名

let [,num2] = [10,11];
console.log(num2);// 11
复制代码

c. 当右边的数组的值不足以将左边数组的值全部赋值时,会解构失败,对应的值就等于“undefined”

let [num1,num2,num3] = [10,11];
console.log(num1);// 10
console.log(num2);// 11
console.log(num3);// undefind


// 为防止值为undefined,可以在左侧的数组添加默认值
let [num1,num2 = 8,num3 = 12] = [10,11];
console.log(num1);// 10
console.log(num2);// 11
console.log(num3);// 12
// 只有在解构出现undefind的时候默认值才生效,所以这里num2还是为11
复制代码
  • 项目中使用

    • 交换变量
    var a = 1;
    var b = 2;
    // es6之前想要交换变量,需要定义一个临时变量
    var tmp = a;
    a = b;
    b = tmp;
    
    
    // es6之后
    [a, b] = [b, a]
    复制代码
    • 解析函数返回的数组
      • 函数返回数组是一个很常见的场景,在获取数组后,我们经常会提取数组中的元素进行后续处理
    function fun(){
      return [10, 11]
    }
    let [num1, num2] = fun();
    console.log(num1); // 10
    console.log(num2); // 11
    复制代码
    • 嵌套数组的解构
    let [num1, num2, [num3]] = [10, [11, 12], [13]];
    console.log(num1); //10
    console.log(num2); //[11, 12]
    console.log(num3); //13
    复制代码
    • 函数参数解构
    • 当函数的参数为数组类型时,可以将实参和形参进行解构
    function fun([arg1, arg2]){
      console.log(arg1); //10
      console.log(arg2); //11
    }
    fun([10, 11]);
    复制代码
  1. 对象的解构赋值

右侧解构对象的属性名和左侧定义对象的变量名必须相同

  1. 对象解构赋值原理和使用

    1. 没有匹配到的变量名在解构时赋值为’undefined’
    2. key和value名称相同时可以简写,不同时左侧必须严格按照key:value格式书写
    3. 原理:找到左右两侧相同的属性名(key),然后再赋给对应的变量(value),真正被赋值的是value部分,并不是key的部分
    4. 解构赋值的两个对象的属性不必是一一对应的,只要key部分名字相同就能解构成功
let {m, n, o} = {m: 'yangf', n: '18'};
console.log(m); // 'yangf'
console.log(n); // '18'
console.log(o); // undefined


let {m:name,n:age} = {m:'yangf',n:18}
console.log(name); // 'yangf'
console.log(age); // '18'
console.log(m); // ReferenceError: m is not defined


let {n:age,m:name} = {m:'yangf',n:18}
console.log(name); // 'yangf'
console.log(age); // '1
复制代码
  1. 对象解构的默认值

    1. 解构的时候可以设置默认值,默认值生效的条件是对应的属性值严格等于undefined
    2. 属性名和变量名不同时,默认值是给变量的
let {m, n=18, o:j=1} = {m: 'yangf', n: null};
console.log(m); // 'yangf'
console.log(n); // null (null和undefined不是严格相等)
console.log(j); // 1
console.log(o); // ReferenceError: m is not defined
复制代码
  1. 嵌套对象的解构

    1. 解构时从外层向内层逐步进行
    2. 每一层都遵循相同的解构规则
    3. 当父层对象对应的属性不存在,而解构子层对象时,会出错并抛出异常
let {x:[y,{z:name}]} = {x:['hello',{z:'world'}]};
console.log(y); // 'hello'
console.log(name); // 'world'
console.log(z); // ReferenceError: z is not defined


let {o:[y,{z:name}]} = {x:['hello',{z:'world'}]};
console.log(y); // undefined is not iterable 
复制代码

右侧外层的属性名是x,左侧外层属性名是o,两者并不匹配,所以o会解构得到“undefined”。而对undefined再次解构想要获取y属性时,相当于调用undefined.y,会抛出异常

  1. 选择性解构对象的属性

    1. 假如一个对象有很多通用的函数,如果只想使用其中的几个函数,可以使用解构赋值
let {min,max} = Math;
console.log(min(1,3)); //1
console.log(max(1,3)); // 3
// 如果只想使用Math对象的min()函数和max()函数,min变量和max变量解构后的值就是Math.min()函数和Math.max()函数,在后面的代码中可以直接使用
复制代码
  1. 函数参数解构

    1. 当函数的参数是一个复杂的对象类型时,可以通过解构去获得想要获取的值
    2. 有时候后台返回一些无用的参数,可以使用解构赋值得到我们想要的某一些参数
function fun({oldName,{newName:{firstName:name}}}) {
    console.log(oldName + 'is' + name);
}
let people = {
    age:18,
    oldName:'yangf',
    newName:{
      firstName:'yang',
      lastName:'feng'
    }
};
fun(people);// yangf is yang
复制代码

扩展运算符

用3个点表示(…),用于将一个数组或类数组对象转换为用逗号分隔的值序列

const array = [1,2,3,4];
const str = 'string';
console.log(...array); // 1 2 3 4
console.log(...str);// s t r i n g
复制代码
  1. 扩展运算符替代apply函数

    a. 将数组转化为函数参数

console.log(Math.min(...[1,2,3])); // 1 


function add(num1,num2){
  return num1 + num2;
}
const arr = [1, 2];
add(...arr); // 3
复制代码
  1. 扩展运算符代替concat函数合并数组

const arr1 = [1, 2];
const arr2 = [3, 4];
console.log([...arr1, ...arr2]); // [1, 2, 3, 4]
复制代码
  1. 扩展运算符转换Set得到去重的数组

const arr = [1, 2, 3, 4, 4, 4];
console.log([...new Set(arr)]); // [1, 2, 3, 4]
复制代码
  1. 扩展运算符用于对象和数组的克隆

    // 例1
    let obj = {name: 'yangf'};
    let copyObj = {...obj};
    copyObj.name = 'yfeng';
    console.log(obj.name); // yangf
    // 例2
    let obj = {people:{name: 'yangf'}};
    let copyObj = {...obj};
    copyObj.people.name = 'yfeng';
    console.log(obj.people.name); // yfeng
    复制代码

    a. 例1对copyObj进行改变,不会影响obj

    b. 例2对copyObj进行改变,影响了obj

    c. 说明扩展运算符不是严格意义上的深拷贝

    d. 如果数组的元素或者对象的属性是基本数据类型,则支持深拷贝;如果是引用数据类型,则不支持深拷贝

    e. 扩展运算符可以代替concat函数,所以concat函数也具备以上特性

rest运算符

同样使用3个点表示(…),作用与扩展运算符相反,用于将以逗号分隔的值序列转换成数组

  1. rest运算符与解构组合使用

    1. 解构会将相同数据结构对应的值赋给对应的变量,当我们想将其中的一部分值统一赋给一个变量时,可以使用rest运算符

      a. 想要使用rest运算符进行解构,则rest运算符对应的变量应该放在最后一位,否则就会抛出异常

      b. 对象和数组使用方法相同

    let arr = [1, 2, 3, 4];
    let [arg1,...arg2] = arr;
    console.log(arg1); // 1
    console.log(arg2); // [2, 3, 4]
    
    
    let arr = [1, 2, 3, 4];
    let [...arg1,arg2] = arr;
    // SyntaxError: Rest element must be last element
    复制代码
    1. rest运算符代替arguments处理函数参数

      a. 当我们不确定函数传人参数的个数时使用

function fun() {
  for(let arg of arguments) {
    console.log(arg)
  }
};
fun(1,2,3,4); // 1 2 3 4


function fun(...args) {
  for(let arg of args) {
    console.log(arg)
  }
};
fun(1,2,3,4); // 1 2 3 4
复制代码

扩展运算符和rest运算符两者是互为逆运算的,扩展运算符是将数组分割成单独的序列,而rest运算符是将单独的序列合并成一个数组

  1. 当3个点(…)出现在函数的形参上或者出现在赋值等号的左侧,则表示它为rest运算符。
  2. 当3个点(…)出现在函数的实参上或者出现在赋值等号的右侧,则表示它为扩展运算符。

模板字符串

  1. 字符串输出

    1. 使用加号拼接字符串会丢失缩紧和换行符
    2. 使用模板字符串则不会
const str = 'my name is yangf,'+
    'i am a girl';
console.log(str); // my name is yangf,i am a girl


const str = `my name is yangf,
    i am a girl`;
console.log(str);
/*
 my name is yangf,
    i am a girl
*/
复制代码
  1. 字符串变量值传递

    1. 都是使用${}包裹变量

      a. 模板字符串中传递基本数据类型的变量

      b. 模板字符串中传递表达式

      c. 模板字符串中传递复杂引用数据类型的变量

const name = 'yangf';
console.log(`my name is ${name}`); // my name is yangf


// 表达式调用
const x = 1, y = 2;
console.log(`${x} + ${y * 2} = ${x + y *2}`); // 1 + 4 = 5


// 函数调用
function fun() {
    return 'name is';
};
console.log(`my ${fun()} yangf`); // my name is yangf


// 嵌套使用
const people = [{
    name:'xiaom',
    age:'18'
}, {
    name:'xiaoh',
    age:'20'
}];
const peopleTable = function(people){
  return `<table>${people.map(item => `
    <tr><td>${item.name}<td></tr>
    <tr><td>${item.age}<td></tr>
`).join('')}</table>`
};
console.log(peopleTable(people));
/*
<table>
    <tr><td>xiaom<td></tr>
    <tr><td>18<td></tr>
    
    <tr><td>xiaoh<td></tr>
    <tr><td>20<td></tr>
</table>
复制代码

箭头函数

省去了function关键字,函数体会被一个大括号括起来

如果函数的参数只有一个,则可以省略小括号

如果函数体只有一行,则可以省略大括号和return关键字

  1. 特点

    1. 语法简洁
    2. 不绑定this
      1. this永远指向函数的调用者
      2. 在箭头函数中,this指向的是定义时所在的对象,而不是使用时所在的对象
    3. 不支持call和apply
      1. 箭头函数并没有自己的this,而是继承父作用域中的this
    4. 不绑定arguments
      1. 在箭头函数中使用arguments,同样也就无法使用caller和callee属性
      2. arguments中有一个callee的属性,指向函数本身
      3. caller属性中保存着调用当前函数的函数的引用
      4. 可以使用rest运算符来获取参数
    5. 支持嵌套
      1. 一个参数会以管道的形式经过两个函数处理
      2. 第一个函数处理完的输出将作为第二个函数的输入
      3. 两个函数运算完后输出最后的结果
[1,3,2,4].sort((x,y)=>x-y);//[1,2,3,4]


const person = {
  name:'yangf',
  age:18,
  say:function(){
    console.log(`my name is ${this.name}`)
  },
  say1:()=>{
    console.log(`my age is ${this.age}`)
  }
};
// this指向函数的调用体,即person本
console.log(person.say());// my name is yangf
// 在执行person.say1()函数时,say1()函数中的this会指向外层作用域,而person的父作用域就是window
console.log(person.say1());// my age is undefined


const fun = ()=>{
  console.log(arguments);
};
fun(1,2);// ReferenceError: arguments is not defined


const fun = (...args)=>{
  console.log(args);
};
fun(1,2);// [1,2]


// 嵌套
const fun = (...args) => val => args.reduce((a,b) => b(a),val);
const num1 = a => a + 1; //6
const num2 = a => a * 2; 
const add = fun(num1,num2); // 调用了fun()函数,并传入num1和num2两个参数,返回的是一个函数,在函数中使用reduce()函数先后调用传入的两个处理函数。
console.log(add(5)); //fun()函数中的val为5,在第一次执行reduce()函数时,a为5,b为num1()函数,实际相当于执行5 + 1 = 6,并返回了计算结果


// 在第二次执行reduce()函数时,a为上一次返回的结果6,b为num2()函数,实际相当于执行6×2 =12,因此最后输出
复制代码
  1. 箭头函数不适用的场景

    1. 不适合作为对象的函数

      a. 使用箭头函数定义对象字面量的函数,其中的this将会指向外层作用域,并不会指向对象本身

    2. 不能作为构造函数,不能使用new操作符

      a. 构造函数是通过new操作符生成对象实例的,生成实例的过程也是通过构造函数给实例绑定this的过程,而箭头函数没有自己的this

    3. 没有prototype属性

      a. 因为在箭头函数中是没有this的,也就不存在自己的作用域

    4. 不适合将原型函数定义成箭头函数

      a. 在给构造函数添加原型函数时,如果使用箭头函数,其中的this会指向全局作用域window,而并不会指向构造函数

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享