02-新语法

nobility 发布于 2022-09-22 02-ES6 2902 次阅读


新语法

注意

使用这些新增的指定函数默认值、解构赋值和剩余参数的形式就无法在函数内部使用严格模式;原因是解析引擎会优先解析函数参数部分后解析函数体,若函数参数中出现了违背严格模式时还为发现函数体是否开启严格模式,索性若使用这些形式在开启严格模式就报错

换句话说就是当函数参数部分使用了新语法就会报错

为函数形参指定默认值

  • 新增可以通过解构赋值的方式指定默认值:一般指定函数尾部形参,这样复合规范
    • 指定默认值后该函数的 length 属性将返回未指定默认值的形参个数
    • 指定默认值的函数默认无法再声明同名函数将前面的覆盖
    • 指定默认值后该形参会像使用 let 关键字声明的变量一样,就无法用 function(x = x){} 的形式了
    • 函数形参括号属于父作用域,函数体属于子作用域

模板字符串

基本用法:反引号进行包裹,该字符串会保留换行和缩进,使用 ${js代码} 插值语法可以插入内容

let str = "str";
console.log(`Hello ${str} World`); //插入变量,Hello str World
// console.log(`Hello ${str1} World`);  //插入变量,若变量不存在会报错
console.log(`Hello ${1 + 1} World`); //表达式,Hello 2 World
console.log(`Hello ${(function () { return "str"; })()} World`); //插入函数返回值,Hello str World
console.log(`Hello ${{}} World`);   //插入对象类型默认调用toString转化为相应的字符串,Hello [object Object] World
console.log(`Hello ${`hello`} World`);  //甚至可以嵌套内部的模板字符串,Hello hello World

与函数连用,也称为标签模板:函数后不再使用圆括号进行调用,而是直接跟一个模板字符串,该模板字符串会按照一定规则当作函数的参数

参数规则:第一个参数是模板字符串未被替换部分的分隔数组形式,之后的就是该模板字符串中的替换部分作为函数的可变参数,所以为了使用这种方式调用函数则函数的形式参数一般这样定义 function fun(strArr,...value){}

console.log`hello`; //就算只有一个单独的模板字符串也会存放到一个数组中
//同等于
console.log(["hello"]);

console.log`hello${1 + 1}world`; //被插值分隔的字符串会存放到数组中当第一个参数
//同等于
console.log(["hello", "world"], 2);

console.log`hello${1 + 1}`; //被插值分隔的肯定会有两部分,后面若没有内容则是空字符串
//同等于
console.log(["hello", ""], 2);

箭头函数

  • 新增使用 (参数列表)=>{函数体} 符号定义函数
    • 若参数是一个则可以省略圆括号
    • 若函数体只有一条 return 语句则可以省略大括号,必须省略 return 关键字
    • 若此时返回的一个对象,为了对象的大括号与函数体的大括号区分所以必须为对象包裹一个圆括号
  • 注意点
    • 箭头函数中没有 thisthis 引用的是父非箭头函数的 this,所以在定义对象的方法时就不要使用箭头函数,在使用回调函数时使用箭头函数从而不必担心会指向全局对象了
    • 箭头函数不能当作构造函数来使用,也就是不能 new
    • 箭头函数中不能使用 arguments 对象,使用可变参数代替
var p = "函数外部"; //不使用let的原因:var声明的变量属于全局对象,而let不是
(function () {
    let p = "函数内部";
    var obj = {
        p: "对象内部",
        arrowfun: () => {
            console.log('箭头函数:', this.p);   //父函数的this相同
        },
        fun: function () {
            console.log('普通函数', this.p);    //指向该对象
        }
    };
    obj.arrowfun(); //箭头函数: 指定的对象
    obj.fun();  //普通函数 对象内部
    console.log(this.p);    //箭头函数: 指定的对象
}).call({ p: "指定的对象" });   //修改父函数的this指向

扩展运算符

剩余参数

  • 新增可以通过 ...形参 的方式指定剩余参数(可变参数):实质是将剩余的参数装到一个数组中
    • 使用剩余参数后该函数的 length 属性将返回不包括剩余参数的形参个数
    • 可以使用数组中的任意方法
    • 只能出现在函数形参中的最后

数组对象解构合并

  • 新增可以通过 ...数组|对象 的方式将数组元素以逗号分隔的形式:要注意的是会将空位转化为 undefined
    • 可以作用于函数参数圆括号上
    • 可以作用于数组方括号中
    • 可以作用与对象属性中,作为对象的属性是拷贝,并不是引用,所以修改合并的对象属性不会影响原来的属性
console.log(...[1, 2, 3]);  //1 2 3
//相当于
console.log(1, 2, 3);   //1 2 3

console.log([1, ...[2, 3]]);    //[ 1, 2, 3 ]
//相当于
console.log([1, 2, 3]); //[ 1, 2, 3 ]

console.log(...[1, , 3]);  //1 undefined 3

console.log({ a: 1, ...{ b: 2, c: 3 } });   //{ a: 1, b: 2, c: 3 }
//相当于
console.log({ a: 1, b: 2, c: 3 });  //{ a: 1, b: 2, c: 3 }

剩余属性

  • 可以通过 ...变量 的方式将数组或对象的剩余属性以数组或对象形式赋值给该变量
/*可变参数解构数组*/
let [a, ...b] = [1, 2, 3];
console.log(a, b);   //1 [ 2, 3 ]
/*可变参数解构对象*/
let { a, ...b } = { a: 1, b: 2 };
console.log(a, b);  //1 { b: 2 }

字符串解构

  • 可以通过 ...字符串 的形式将字符串转化成字符数组
console.log([..."hello"]);  // [ "h", "e", "l", "l", "o" ]

对象属性的扩展写法

简洁属性和属性表达式不能同时使用,否则会报错

  • 简洁属性:字面量方式创建对象时,可以直接写入变量和函数,此时变量名和函数命名就的该对象的属性名和方法名
    • 简洁属性若是方法,该方法能使用 new 关键字,这也是与非简洁属性方法的唯一区别
let id = "id";
let obj1 = {
    id,
    fun() { }
};
//同等于
let obj2 = {
    id: "id",
    fun: function () { }
}
  • 属性表达式:字面量方式创建对象时,属性名允许使用方括号,方括号内可以使用表达式、变量等 JavaScript 代码,最终方括号中的内容会被转化为字符串
let obj = {
    ["p"+"1"]:1,
    ["f"+"n"](){}
}
console.log(obj);	//{ p1: 1, fn: [Function: fn] }

//let obj = {
//    "p"+"1":1,	//报错
//		"f"+"n"		//报错
//}

迭代器

若想让一个对象可以使用 for...of 增强型 for 循环、解构赋值、扩展运算符和 Array.form() 方法转化为数组,就必须实现迭代器接口,也就是说该对象中能直接调用 [Symbol.iterator]() 方法,他可以是继承的也可以是自己独有的

[Symbol.iterator]() 方法应该返回一个迭代器对象,该迭代器对象中应该有一个至少 u next() 方法,next() 方法中需要返回一个状态对象 {value:value,done:true|false}:value 代表当前迭代要返回的值,done 代表是否结束迭代

let obj = {
    data: [1, 2, 3],
    [Symbol.iterator]() {
        let index = 0;  //游标
        function next() {
            if (index < this.data.length) { //还没到最后一个元素
                return {
                    value: this.data[index++],  //返回当前值,并且指针后移
                    done: false //状态是未结束
                };
            } else {    //到最后一个
                return { value: undefined, done: true };    //返回undefuned,状态是结束
            }
        }
        return {
            next:next.bind(this)    //必须返回一个迭代器对象,并且有next方法
        };
    }
};

for (const val of obj) {
    console.log(val);   
}
// 1
// 2
// 3
console.log([...obj]);  // [ 1, 2, 3 ]

可以选择增加一个 return() 方法,该方法在 for...of 时碰到 brack 或出错时触发,该方法返回一个对象,应该有 done 属性表示迭代结束

let obj = {
    data: [1, 2, 3],
    [Symbol.iterator]() {
        let index = 0;  //游标
        function next() {
            if (index < this.data.length) { //还没到最后一个元素
                return {
                    value: this.data[index++],  //返回当前值,并且指针后移
                    done: false //状态是未结束
                };
            } else {    //到最后一个
                return { value: undefined, done: true };    //返回undefuned,状态是结束
            }
        }
        return {
            next: next.bind(this),    //必须返回一个迭代器对象,并且有next方法
            return() {   //可以选择是否有一个return方法
                console.log("brack 触发了 或 抛出了异常");
                return { done: true };
              //必须返回一个对象
              //此方法定义在方法中会与方法中的return关键字有冲突,必须定义在对象上
            }
        };
    }
};

for (const val of obj) {
    console.log(val);
    if (val === 2) {
        break;
        // throw new Error();
    }
}
// 1
// 2
// brack 触发了 或 抛出了异常

Generator 函数

也称为生成器函数

声明
  • 于普通的函数类似,只不过使用星号标记方法名,但是不能作为构造函数那样使用
  • 若执行 Generator 函数,函数不会立刻执行,而是返回一个迭代器对象,所以使用该函数可以定义迭代器接口
  • 返回的迭代器对象中有 [Symbol.iterator]() 方法,并且该方法返回值就是迭代器本身,所以可以对该迭代对象使用 for...of... 和扩展运算符
  • 使用该返回的迭代器对象可以像代码调试断点那样暂停和恢复执行函数
function *fun(){	//只要函数名前面有星号即可,不必在意空格
  yield 1;	//可以理解为断点
  yield 2;
  yield 3;
}
let gen = fun();
console.log(gen); //Object [Generator] {}

console.log(gen.next());  //{ value: 1, done: false }	//断点表达式的值和还未调试完
console.log(gen.next());  //{ value: 2, done: false }
console.log(gen.next());  //{ value: 3, done: fasle }
console.log(gen.next());  //{ value: undefined, done: true }	//函数执行结束返回值是undefined,调试完

console.log(gen === gen[Symbol.iterator]());
//对Generator直接使用for...of增强型for循环、解构赋值、扩展运算符和Array.form()方法转化为数组的前提
for (const val of fun()) {
  console.log(val);
}
// 1
// 2
// 3
console.log([...fun()]);  //[ 1, 2, 3 ]
Yield 表达式
  • 只能用于 Generator 函数中,所以在 Generator 函数中使用回调时特别要注意不能使用
  • 代表函数执行暂停位置,并返回包含暂停位置表达式的值和迭代器状态的对象,若 yield 后没有跟着任何表达式则相当于 undefined
  • yield 表达式参与计算时需要用圆括号包裹,否则会报错,但是作为赋值语句的右侧或函数的参数时无需加圆括号
  • yieldreturn 的区别
    • return 会终止整个 Generator 函数的执行,并返回 retuen 后的表达式的值和迭代器状态是 true 的对象;若没有 return 语句,则函数一直会执行到最后,返回 undefined 和迭代器状态是 true
    • yield 不会终止 Generator 函数的执行,并返回 yield 后的表达式的值和迭代器的状态是 false 的对象
function* fun() {
  yield;  //返回对象的value值为undefined
  yield 2;
  return 3;
  yield 4;  //永远不会执行到,因为return会终止掉整个函数
}
let gen = fun();
console.log(gen.next());  //{ value: undefined, done: false }
console.log(gen.next());  //{ value: 2, done: false }
console.log(gen.next());  //{ value: undefined, done: true }
Yieid*表达式
  • 用来简化在一个 Generator 函数中执行另一个 Generator 函数
function *innerGen() {  //另一个Generator函数
  yield 'a';
  yield 'b';
}

function *outerGen() {
  yield 'x';
  for (let val of innerGen()) { //执行另一个Generator函数
    yield val;
  }
  yield 'y';
}
console.log([...outerGen()]);  //[ 'x', 'a', 'b', 'y' ]
// 相当于
function *fun(){
  yield "x";
  yield "a";
  yield "b";
  yield "y";
}
console.log([...fun()]);  //[ 'x', 'a', 'b', 'y' ]
//简写方式
function *fn(){
  yield "x";
  yield* innerGen();
  yield "y" 
}
console.log([...fn()]);  //[ 'x', 'a', 'b', 'y' ]
  • 也可以执行另一个只要是实现迭代器接口的对象
function *gen() {
  yield* [1, 2, 3];
}
//相当于
function *gn() {
  yield 1;
  yield 2;
  yield 3;
}
console.log([...gen()]);  //[ 1, 2, 3 ]
console.log([...gn()]); //[ 1, 2, 3 ]


function *gen() {
  yield* "123";
}
//相当于
function *gn() {
  yield "1";
  yield "2";
  yield "3";
}
console.log([...gen()]);  //[ '1', '2', '3' ]
console.log([...gn()]); //[ '1', '2', '3' ]
Next 方法

要注意的是, 在第一次调用 next() 方法时只是启动 Generator 函数执行,没有 yield 表达式,所以传入任何参数都是无意义的

参数 说明
无参 使用 undefined 替换 Generator 函数中上一次 yield 表达式的值,供向下执行时使用
一个参数 使用该参数替换 Generator 函数中上一次的 yield 表达式的值,供向下执行时使用
function *fun(){
  let temp;
  temp = yield 1;
  console.log("Generator中的yield替换值是:"+temp);
  temp = yield 2;
  console.log("Generator中的yield替换值是:"+temp);
}
let gen = fun();
gen.next();
gen.next();
gen.next();
// Generator中的yield替换值是:undefined
// Generator中的yield替换值是:undefined

gen = fun();
gen.next("第一次的参数");  //第一次执行时只是启动,还没有yield表达式,所以传入任何参数都是无意义的
gen.next("第二次的参数");  //从第二次执行开始,将此开始的yield表达式替换为传入的参数
gen.next("第三次的参数");
// Generator中的yield替换值是:第二个yield的参数
// Generator中的yield替换值是:第三个yield的参数
Throw 方法

由于 next 方法只会将参数只是替换为表达式的值,无法替换为一条语句,若想将 yield 替换为一条抛出错误的语句时就必须依赖于 throw() 方法

  • 若内部没有处理该错误,则会抛给调用 throw() 方法处
  • 执行 throw() 方法后也会像 next() 方法执行后指向下一个断点
function* fun() {
  try {
    yield;  //相当于在这里 throw new Error;
  } catch (e) {
    console.log("捕捉到了错误");
  }
};
let gen = fun();
gen.next(); //先将函数启动,若启动函数就抛出错误则相当于在外部直接抛出错误

gen.throw(new Error()); //将yield替换为 throw new Error;语句
//捕捉到了错误
Return 方法

同样的,若想将 yield 替换为一条 return 语句时就必须依赖 return() 方法

  • 若内部有 finally 语句块,就像平常函数一样会进入 finally 语句块中之后在结束函数执行
function* fun() {
  yield 1;
  yield 2;	//相当于 return "结束"
  yield 3;
};
let gen = fun();
console.log(gen.next()); //{ value: 1, done: false }
console.log(gen.return("结束"));  //{ value: '结束', done: true }
console.log(gen.next());  //{ value: undefined, done: true }

链判断运算符

  • 可以通过 变量?.属性或操作 的形式,若左侧变量存在则执行右侧操作,若左侧变量不存在则返回 undefined
let outerObj = {
    innerObj:{
       fun(){return "fun"}
    }
}
console.log(outerObj?.innerObj?.fun?.()?.length);	//3
//依次的从左到右,outerObj存在则会向内寻找innerObj
//innerObj存在则会向内寻找fun
//fun存在则会调用该函数
//该函数返回值不是undefined或null则会获取length属性
//若这之中有一个不存在则都会返回undefined

空判断运算符

  • 可以通过 变量??默认值 的方式指定若变量不存在时会返回默认值
a = false;
console.log( a || "default");  //default

a = "";
console.log( a || "default");  //default

a = 0;
console.log( a || "default");   //default
//用判空运算符解决
a = false;
console.log( a ?? "default");   //false

a = "";
console.log( a ?? "default");   //""

a = 0;
console.log( a ?? "default");   //0
加油啊!即便没有转生到异世界,也要拿出真本事!!!\(`Δ’)/
最后更新于 2022-09-22