新手咋地

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

es6

let

参考文档https://es6.ruanyifeng.com/

声明变量

  1. 变量不能同时重复声明

    let star = '罗志祥'
    let star = '小猪'
    //报错
  2. 块级作用域

    为什么需要块级作用域

    第一种场景,内层变量可能会覆盖外层变量。

    var tmp = new Date();

    function f() {
     console.log(tmp);
     if (false) {
       var tmp = 'hello world';
    }
    }

    f(); // undefined

    上面代码的原意是,if代码块的外部使用外层的tmp变量,内部使用内层的tmp变量。但是,函数f执行后,输出结果为undefined,原因在于变量提升,导致内层的tmp变量覆盖了外层的tmp变量。

    第二种场景,用来计数的循环变量泄露为全局变量。

    var s = 'hello';

    for (var i = 0; i < s.length; i++) {
     console.log(s[i]);
    }

    console.log(i); // 5

    上面代码中,变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。

    let实际上为 JavaScript 新增了块级作用域。

    function f1() {
     let n = 5;
     if (true) {
       let n = 10;
    }
     console.log(n); // 5
    }

    上面的函数有两个代码块,都声明了变量n,运行后输出 5。这表示外层代码块不受内层代码块的影响。如果两次都使用var定义变量n,最后输出的值才是 10。

    ES6 允许块级作用域的任意嵌套。

    {{{{
    {let insane = 'Hello World'}
     console.log(insane); // 报错
    }}}};

    上面代码使用了一个五层的块级作用域,每一层都是一个单独的作用域。第四层作用域无法读取第五层作用域的内部变量。

    内层作用域可以定义外层作用域的同名变量。

    {{{{
     let insane = 'Hello World';
    {let insane = 'Hello World'}
    }}}};

    块级作用域的出现,实际上使得获得广泛应用的匿名立即执行函数表达式(匿名 IIFE)不再必要了。

    // IIFE 写法
    (function () {
     var tmp = ...;
     ...
    }());

    // 块级作用域写法
    {
     let tmp = ...;
     ...
    }

    块级作用域中函数的声明

    function f() { console.log('I am outside!'); }

    (function () {
     if (false) {
       // 重复声明一次函数f
       function f() { console.log('I am inside!'); }
    }

     f();
    }());

    如果在es5中,控制台中会输出I am inside!,因为if内声明的函数会被提升到函数头部,实际运行的代码如下

    // ES5 环境
    function f() { console.log('I am outside!'); }

    (function () {
     function f() { console.log('I am inside!'); }
     if (false) {
    }
     f();
    }());

    如果在es6中,es6引入了块级作用域,明确允许在块级作用域中声明函数,类似于let,在块级作用域之外不可以引用,理论上会得到I am outside!但是,实际上是会报错的,如果改变了块级作用域内声明的函数的处理规则,显然会对老代码产生很大影响。为了减轻因此产生的不兼容问题,ES6 在附录 B里面规定,浏览器的实现可以不遵守上面的规定,有自己的行为方式

    • 允许在块级作用域内声明函数。

    • 函数声明类似于var,即会提升到全局作用域或函数作用域的头部。

    • 同时,函数声明还会提升到所在的块级作用域的头部。

    // 浏览器的 ES6 环境
    function f() { console.log('I am outside!'); }
    (function () {
    var f = undefined;
    if (false) {
    function f() { console.log('I am inside!'); }
    }

    f();
    }());
    // Uncaught TypeError: f is not a function

    考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。

    // 块级作用域内部的函数声明语句,建议不要使用
    {
    let a = 'secret';
    function f() {
    return a;
    }
    }

    // 块级作用域内部,优先使用函数表达式
    {
    let a = 'secret';
    let f = function () {
    return a;
    };
    }

    另外,还有一个需要注意的地方。ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域。

    // 第一种写法,报错
    if (true) let x = 1;

    // 第二种写法,不报错
    if (true) {
    let x = 1;
    }

    上面代码中,第一种写法没有大括号,所以不存在块级作用域,而let只能出现在当前作用域的顶层,所以报错。第二种写法有大括号,所以块级作用域成立。

    函数声明也是如此,严格模式下,函数只能声明在当前作用域的顶层。

    // 不报错
    'use strict';
    if (true) {
    function f() {}
    }

    // 报错
    'use strict';
    if (true)
    function f() {}
  3. 不存在变量提升

    console.log(song);
    let song = '恋爱达人';
    //报错
  4. 不影响作用域链

    {
    let school = '科师';
    function fn(){
    console.log(school);
    }
    fn();
    }
    //输出科师
    1. 暂时性死区

      只要块级作用域存在let命令,他所生命的变量就绑定这个区,不受外部影响

      var tmp=123;
      if(true) {
      tmp= abc;//REferenceError,在此处赋值
      let tmp;
      }

      ES6中,如果代码块用存在let或者const命令,这些区块对这些命令生成的变量,从一开始就形成了封闭的作用域。凡是let或者const声明之前使用些变量就会报错

      上述代码,存在全局变量tmp,但是块级作用域中let又声明了一个局部变量tmp,导致let绑定了这个块级作用域,所以let声明之前,对tmp赋值都会报错

      总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)

      if (true) {
      // TDZ开始
      tmp = 'abc'; // ReferenceError
      console.log(tmp); // ReferenceError

      let tmp; // TDZ结束
      console.log(tmp); // undefined

      tmp = 123;
      console.log(tmp); // 123
      }

      在let之前声明变量tmp之前,都属于变量tmp的死区

      typeof在let声明之前使用也会报错

      一些隐蔽的死区

      function bar(x = y, y = 2) {
       return [x, y];
      }

      bar(); // 报错

      因为x默认值等于另一个参数y,而此时y还没有声明,属于死区。如果y的默认值为x则不会,因为x已经声明了

      // 不报错
      var x = x;

      // 报错
      let x = x;
      // ReferenceError: x is not defined

      使用let声明变量时,只要变量在还没有声明完成前使用,就会报错。上面这行就属于这个情况,在变量x的声明语句还没有执行完成前,就去取x的值,导致报错”x 未定义“

const声明常量

//声明常量
const A = '张三'
  1. 一定要赋初始值

    const A = '张三';

  2. 一般常量使用大写(潜规则,如果是小写也不报错)

  3. 常量的值不能修改

  4. 块儿级作用域

    {

    const PLAYER = 'UZI'

    }

    同样存在暂时性死区,只能在生命的位置后面使用

  5. 对于数组和对象元素的修改不算做对常量的修改,不会报错(因为所指向数组和对象的地址未发生改变)

    本质:const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心

    如果真的想将对象冻结,应该使用Object.freeze方法。

    const foo = Object.freeze({});

    // 常规模式时,下面一行不起作用;
    // 严格模式时,该行会报错
    foo.prop = 123;

    上面代码中,常量foo指向一个冻结的对象,所以添加新属性不起作用,严格模式时还会报错。

    除了将对象本身冻结,对象的属性也应该冻结。下面是一个将对象彻底冻结的函数。

    var constantize = (obj) => {
    Object.freeze(obj);
    Object.keys(obj).forEach( (key, i) => {
    if ( typeof obj[key] === 'object' ) {
    constantize( obj[key] );
    }
    });
    };
  6. 不可重复声明

顶层对象的属性

顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象。ES5 之中,顶层对象的属性与全局变量是等价的。

window.a = 1;
a // 1

a = 2;
window.a // 2

这种情况出现的问题:没法在编译时就报出变量未声明的错误,只有在运行时才知道。程序员容易不知不觉创建了全局变量。顶层对象的属性是可以到输出写的,不利于模块化开发。window对象是有实体含义的,顶层对象是一个有含义的 对象也不合适

es为了保持兼容性,保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。

var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1

let b = 1;
window.b // undefined

globalThis对象

JavaScript 语言存在一个顶层对象,它提供全局环境(即全局作用域),所有代码都是在这个环境中运行。但是,顶层对象在各种实现里面是不统一的。

  • 浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window

  • 浏览器和 Web Worker 里面,self也指向顶层对象,但是 Node 没有self

  • Node 里面,顶层对象是global,但其他环境都不支持。

同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this关键字,但是有局限性。

  • 全局环境中,this会返回顶层对象。但是,Node.js 模块中this返回的是当前模块,ES6 模块中this返回的是undefined

  • 函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined

  • 不管是严格模式,还是普通模式,new Function('return this')(),总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全策略),那么evalnew Function这些方法都可能无法使用。

综上所述,很难找到一种方法,可以在所有情况下,都取到顶层对象。下面是两种勉强可以使用的方法。

// 方法一
(typeof window !== 'undefined'
? window
: (typeof process === 'object' &&
typeof require === 'function' &&
typeof global === 'object')
? global
: this);

// 方法二
var getGlobal = function () {
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
};

ES2020 在语言标准的层面,引入globalThis作为顶层对象。也就是说,任何环境下,globalThis都是存在的,都可以从它拿到顶层对象,指向全局环境下的this

垫片库global-this模拟了这个提案,可以在所有环境拿到globalThis

变量的解构赋值

es6允许按照一定模式从数组和对象中提取值,对变量进行复制,这被称为解构赋值

数组的解构

        const F4 = ['小沈阳', '刘能', '赵四', '宋小宝'];
let [xiao, liu, zhao, song] = F4;
console.log(xiao);
console.log(liu);
console.log(zhao);
console.log(song);
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

image-20211021093212591

另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。

let [x, y] = [1, 2, 3];
x // 1
y // 2

let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4

如果等号右边不是数组,那么会报错

对象的解构

        const ZHAO = {
name: '赵本山',
age: '不详',
xiaopin: function () {
console.log("我可以 演小品");
}
}
let { name, age, xiaopin } = ZHAO;
console.log(name);
console.log(age);
console.log(xiaopin);
xiaopin();

image-20211021093701844

如果以后遇到方法频繁调用,可以想到解构赋值

用法

对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量。

// 例一
let { log, sin, cos } = Math;

// 例二
const { log } = console;
log('hello') // hello

上面代码的例一将Math对象的对数、正弦、余弦三个方法,赋值到对应的变量上,使用起来就会方便很多。例二将console.log赋值到log变量。

 

也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
foo // error: foo is not defined

上面代码中,foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo

注意

(1)如果要将一个已经声明的变量用于解构赋值,必须非常小心。

// 错误的写法
let x;
{x} = {x: 1};
// SyntaxError: syntax error

上面代码的写法会报错,因为 JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题。

// 正确的写法
let x;
({x} = {x: 1});

上面代码将整个解构赋值语句,放在一个圆括号里面,就可以正确执行。关于圆括号与解构赋值的关系,参见下文。

(2)解构赋值允许等号左边的模式之中,不放置任何变量名。因此,可以写出非常古怪的赋值表达式。

({} = [true, false]);
({} = 'abc');
({} = []);

上面的表达式虽然毫无意义,但是语法是合法的,可以执行。

(3)由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。

let arr = [1, 2, 3];
let {0 : first, [arr.length - 1] : last} = arr;
first // 1
last // 3

上面代码对数组进行对象解构。数组arr0键对应的值是1[arr.length - 1]就是2键,对应的值是3。方括号这种写法,属于“属性名表达式”

字符串的解构赋值

字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。

let {length : len} = 'hello';
len // 5

数值和布尔值的解构赋值

解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。

let {toString: s} = 123;
s === Number.prototype.toString // true

let {toString: s} = true;
s === Boolean.prototype.toString // true

上面代码中,数值和布尔值的包装对象都有toString属性,因此变量s都能取到值。

解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefinednull无法转为对象,所以对它们进行解构赋值,都会报错。

let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError

函数的解构赋值

函数的参数也可以使用解构赋值。

function add([x, y]){
return x + y;
}

add([1, 2]); // 3

上面代码中,函数add的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量xy。对于函数内部的代码来说,它们能感受到的参数就是xy

下面是另一个例子。

[[1, 2], [3, 4]].map(([a, b]) => a + b);
// [ 3, 7 ]

函数参数的解构也可以使用默认值。

function move({x = 0, y = 0} = {}) {
return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]

上面代码中,函数move的参数是一个对象,通过对这个对象进行解构,得到变量xy的值。如果解构失败,xy等于默认值。

注意,下面的写法会得到不一样的结果。

function move({x, y} = { x: 0, y: 0 }) {
return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]

上面代码是为函数move的参数指定默认值,而不是为变量xy指定默认值,所以会得到与前一种写法不同的结果。

undefined就会触发函数参数的默认值。

[1, undefined, 3].map((x = 'yes') => x);
// [ 1, 'yes', 3 ]

模板字符串

ES6引入新的声明字符串的方式 ``

  1. 声明

            let str = `我也是一个字符串`;
    console.log(str, typeof str);

    image-20211021094246101

  2. 内容中可以直接出现换行符

            let str = `<ul>
    <li>沈腾</li>
    <li>魏翔</li>
    </ul>`;
  3. 变量拼接

            let str = `魏翔`;
    let out = `${str}是我心中最搞笑的演员!`
    console.log(out);

    image-20211021094924071

简化对象写法

ES6允许在打括号里面直接写入变量和函数,作为对象的属性和方法,这样书写更加简洁

        let name = '张三';
let like = function () {
console.log("吃饭");
}
const SCHOLL = {
name,
like,
improve() {
console.log("吃两碗");
}
}
console.log(SCHOLL);

image-20211021095637304

箭头函数

ES6中允许使用箭头(=>)定义函数

格式

//声明一个函数
let fn = (a,b) => {
return a+b;
}
//调用函数
let result = fn(1,2);
console.log(result);
//3

箭头函数的特性

  1. this是静态的,this始终指向函数声明时所在作用域下的this的值

    //es5
    function getName(){
       console.log(this.name);
    }
    //es6中箭头函数
    let getName2= ()=>{
       console.log(this.name);
    }
    //设置window对象的name属性
    window.name = '张三';
    const SCHOOL = {
       name:"李四";
    }
    //直接调用
    getName();//张三,普通函数直接调用this指向window
    getName2();//张三,是在全局作用域下声明的
    //call方法调用(call方法可以改变this指向)
    getName.call(SCHOOL);//李四
    getName2.call(SCHOOL);//张三,因为是静态的所以不改变this指向
  2. 不能作为构造实例化对象

    let Person = (name,age) =>{
       this.name=name;
       this.age=age;
    }
    let me = new Person('张三',30);
    console.log(me);
    //TypeError: Person is not a constructor(构造器)
  3. 不能死用arguments变量

    let fn = () => {
       console.log(arguments);
       
    }
    fn(1,2,3);
  4. 箭头函数的简写

    1)省略小括号,当形参有且只有 一个的时候

    let add = n =>{
       return n+n;
    }
    console.log(add(9));//18

    2)省略花括号,当代码只有一条语句时,此时return必须省略,而且语句的执行结果就是函数的返回值

    let pow = n => n * n;
    console.log(pow(8));//64

    注意:

    1. 箭头函数适合与this无关的回调,定时器,数组的方法回调

    2. 箭头不适合与this有关的回调,事件回调,对象的方法

函数参数默认值

ES6 允许给函数参数赋初始值

  1. 形参初始值 具有默认值的参数,一般为只要靠后(潜规则)

    function add (a,b,c=10){
    return a + b +c;
    }
    let result = add(1,2);
    console.log(restult);
  2. 与解构赋值结合

    function connect({host="127.0.0.1",username,password,port}){
    console.log(host);
    console.log(username);
    console.log(password);
    console.log(port);
    }
    connect({
    host:'www.example.com',
    username:'root',
    password:'root',
    port:3306
    })

    rest参数

    ES6引入rest参数,用于获取函数的实参,用来代替arguments

    //ES5获取方式
    function date(){
    console.log(arguments);
    }
    date('柏芝','阿娇','思慧');

    image-20211021213158440

    //rest参数
    function date(...args){
    console.log(args);
    }
    date('柏芝','阿娇','思慧');

    image-20211021213330396

扩展运算符

...扩展运算符能将数组转换为逗号分隔的参数序列

//声明一个数组
const tfboys = ['易烊千玺','王源','王俊凯'];
//=>'易烊千玺','王源','王俊凯'
//声明一个函数
function chunwan (){
console.log(arguments);
}
chunwan(...tfboys);//chunwan('易烊千玺','王源','王俊凯')

image-20211021214519866

扩展运算符的应用

//1.数组的合并
const kuaizi =['wangtaili','xiaoyang'];
const fenghuang = ['zengyi','linghua'];
//ES5
//const zuixuanxiaopingguo = kuaizi.concat(fenghuang);
//扩展运算符
const zuixuanxiaopingguo = [...kuaizi,...fenghuang];
console.log(zuixuanxiaopingguo);

 

// 2.数组克隆(浅拷贝)
const sanzhihua = ['E','G','M'];
const sanzhicao = [...sanzhihua];
console.log(sanzhicao);//['E','G','M']
//3.将伪数组转换为真正的数组
const divs = document.querySelectorAll('div');
const divArr = [...divs];
console.log(divArr);
//也可以将arguments转化成数组,但是因为有rest参数,所以没必要进行这步骤操作

symbol基本使用

ES引入了一种新的数据类型Symbol,表示独一无二的值,是一种类似于字符串的数据类型,

Symbol特点

1)Symbol的值是唯一的,用来解决命名冲突

2)Symbol值不能与其他数据进行运算

3)Symbol定义的对象属性不能使用for...in循环遍历,但是可以使用RefLect.ownKeys来获取对象的所有键名

//创建Symbol
let s = Symbol();

let s2 = Symbol('张三');
let s3 = Symbol('张三');
//s2===s3 不相等
let s4 = Symbol.for('张三');
let s5 = Symbol.for('张三');
//s4===s5相等
//不能与其他数据进行运算
let result = s+100;
let result = s>100;
let result = s+s;
//报错

 

Symbol.for()与Symbol()这两种写法都会生成新的Symbol。

他们的区别是前者会被登记在全局环境中供搜索,后者不会。

Symbol创建对象属性

给对象添加属性和方法

        //声明一个对象
let game = {
name: '俄罗斯方块',
up: function () { },
down: function () { }
}
let methods = {
up: Symbol(),
down: Symbol()
}
game[methods.up] = function () {
console.log("我可以改变形状!");
}
game[methods.down] = function () {
console.log("我可以快速下降!");
}
console.log(game);
        let zibao = Symbol('zibao');
//只能通过这个调用
let youxi = {
name: '狼人杀',
[Symbol('say')]: function () {
console.log("我可以发言了");
},
//调用不到
[zibao]: function () {
console.log("我可以自爆");
}
}
console.log(youxi)
//youxi[zibao]

迭代器

迭代器是一种接口,为不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作。

1)ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费

2)原生具有iterator接口的数据(可用 for of 遍历)

a)Array

b) Arguments

c) Set

d) Map

e) String

f) TypedArray

g) NodeList

        const xiyou = ['tangseng', 'sunwukong', 'zhubajie', 'shaseng'];
for (let i of xiyou) {
console.log(i);
}
//for of 中变量表示的是键值
//for in 中变量表示的是键名

image-20211022202745680

3)工作原理

a)创建一个指针对象,指向当前数据结构的起始位置

b)第一次调用对象的next方法,指针自动指向数据结构的第一个成员

c)接下来不断调用next方法,指针一直往后移动,直到指向最后一个成员

d)每调用next方法返回一个包含value和 done属性的对象

        const xiyou = ['tangseng', 'sunwukong', 'zhubajie', 'shaseng'];
let iterator = xiyou[Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

image-20211022203353729

注意:需要自定义遍历数据的时候,要想到迭代器

        const banji = {
name: '终极一班',
stus: [
'ming',
'ning',
'tian'
],
//创建一个iterator对象
[Symbol.iterator]() {
//声明一个索引数
let index = 0;

return {
next: () => {
if (index < banji.stus.length) {
const result = { value: banji.stus[index], done: false }
//索引数自增
index++;
return result;
} else {
return { value: undefined, done: true };
}
}
}
}

}
for (let i of banji) {
console.log(i);
}

生成器

生成器函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同

        //生成器其实就是一个特殊的函数
function* gen() {
console.log('hello generator')
}
let iterator = gen();
iterator.next();

需要调用迭代器中的next方法才能执行

        function* gen() {
console.log('111');
yield '一直没有耳朵';
console.log('222');
yield '一直没有尾巴';
console.log('333');
yield '真奇怪真奇怪';
console.log('444');
}
let iterator = gen();
iterator.next();
iterator.next();
iterator.next();
iterator.next();
//三个yield把gen分成四份,开始到耳朵,耳朵到尾巴,尾巴到奇怪,奇怪到最后

image-20211022213249875

        function* gen() {
// console.log('111');
yield '一直没有耳朵';
// console.log('222');
yield '一直没有尾巴';
// console.log('333');
yield '真奇怪真奇怪';
// console.log('444');
}
let iterator = gen();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

image-20211022213603118

生成器函数参数

        function* gen(arg) {
console.log(arg);
let one = yield 111;
console.log(one);
let two = yield 222;
console.log(two);
let three = yield 333;
console.log(three);
}
let iterator = gen('AAA');
console.log(iterator.next('BBB'));
console.log(iterator.next('CCC'));
console.log(iterator.next('DDD'));

image-20211022214428502

整体的函数传参没有问题,next中也可以传参,是作为上一个yield的参数

例子

        function getUsers() {
setTimeout(() => {
let date = 'renyuanxinxi';
siofjiods
iterator.next(date);
}, 1000)
}
function getOrders() {
setTimeout(() => {
let date = 'dingdanshuju';
iterator.next(date);
}, 1000)
}
function getGoods() {
setTimeout(() => {
let date = 'shangpinshuju';
iterator.next(date);
}, 1000)
}
function* gen() {
let users = yield getUsers();
console.log(users);
let orders = yield getOrders();
console.log(orders);
let goods = yield getGoods();
console.log(goods);
}
let iterator = gen();
iterator.next();

 

Promise

Promise是ES6引入的异步编程的新解决方案。语法上 Promis是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果。

  1. Promise构造函数: Promise (excutor){}

  2. Promise.prototype.then方法

  3. Promise.prototype.catch方法

        //实例化一个对象
const p = new Promise(function (resolve, reject) {
//promise中接受一个函数参数,函数中有两个形参
setTimeout(function () {
// let data = '数据库中用户传的数据';
// resolve(data);
let erro = '数据库中信息传输失败';
reject(erro);
}, 1000);
// resolve表示成功,如果成功,调用then方法中函数为value的函数;reject表示失败,如果失败,调用then方法中reason的函数。
});
//调用Promise中的then方法
p.then(function (value) {
console.log(value);
}, function (reason) {
console.error(reason);
})

Promise封装ajax

const p = new Promise(function(resolve,reject){
//1.创建对象
const xhr = new XMLHttpRequest();
//2.初始化
xhr.open('GET','www.example.com');
//3.发送
xhr.send();
//4.绑定事件,处理相应结果
xhr.onreadystatechange = function(){
//判断
if(xhr.readyState === 4&&xhr.status===200){
resolve(xhr.response)
}else{
reject(xhr.status);
}
}
})
p.then(function(value){
console.log(value);
},function(reason){
console.error(reason);
})

Promise—then方法

        const p = new Promise((resolve, reject) => {
setTimeout(function () {
let data = '数据传输';
resolve(data);
let err = '失败';
reject(err);
}, 1000)
})
//调用then方法,then方法的返回结果是Promise对象,对象状态由回调函数 的执行结果决定
//1.如果回调函数返回的结果是非promise类型的属性,状态为成功,返回值为对象的值
const result = p.then(value => {
console.log(value);
//非promise类型的属性
return 'iloveyou';
//是promise对象,对象中成功,状态为成功
return new Promise((resolve, reject) => {
//resolve('ok');
reject('error');
})
//抛出错误状态为失败
//throw new Error ('出错啦!');
throw '出错啦';
}, reason => {
console.warn(reason)
})
//链式回调,解决回调地狱
p.then(value => {

}, reason => {

}).then((value) => {

})

catch方法

用来指定promise失败的回调,相当于then方法不指定成功的回调

const p = new Promise((resolve,reject)=>{
setTimeout(()=>{
//设置p对象的状态为失败
reject("出错啦");
},1000)
});
p.catch(function(){
console,warn(reason);
});

Set

ES6提供了新的数据结构Set(集合)。它类似于数组,但成员的值都是唯一的,集合实现了iterator接口,所以可以使用「扩展运算符』和「 for...of...』进行遍历,集合的属性和方法:

  1. size 返回集合的元素个数

  2. add 增加一个新元素,返回当前集合

  3. delete 删除元素,返回boolean值

  4. has 检测集合中是否包含某个元素,返回boolean值

  5. clear 清空集合

let s = new Set([1,2,3,4,5]);
//元素个数
// console.log(s2.size);
//添加新的元素
// s2.add('6');
//删除元素
// s2.delete('5');//检测
//console.log(s2.has('糟心事'));
//清空
// s2.clear();
// console.log(s2);
//用for of遍历
for( let v of s2){
console.log(v);
}
Ts = new Set([1,2,3,4,5]);
        let arr = [1, 2, 3, 4, 5, 4, 3, 2, 1];
       //数组去重
       // let result = [...new Set(arr)];
       // console.log(result);
       //...拓展运算符能将数组转换成用逗号连接的参数序列
       //交集
       //filter可以理解为数组的一个用来过滤的方法,回调里会默认收到三个参数,item(item只是这个参数的标识符)就是其中第一个参数,表示每一次调用回调函数时所传递的元素
       let arr2 = [4, 5, 6, 5, 6];
       // let result = [...new Set(arr)].filter(item => {
       //     let s2 = new Set(arr2)
       //     if (s2.has(item)) {
       //         return true;
       //     } else {
       //         return false;
       //     }
       // })
       // let result = [...new Set(arr)].filter(item => new Set(arr2).has(item))
       // console.log(result)
       //并集
       // let union = [...new Set([...arr, ...arr2])];
       // console.log(union);
       //差集
       let diff = [...new Set(arr)].filter(item => !(new Set(arr2).has(item)));
       console.log(diff);

Map

ES6提供了Map 数据结构。它类似于对象,也是键值对的集合。但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map也实现了iterator接口,所以可以使用「扩展运算符』和「 for...of...』进行遍历。Map的属性和方法:

  1. size返回Map的元素个数

  2. set增加一个新元素,返回当前Map

  3. get 返回键名对象的键值

  4. has检测Map中是否包含某个元素,返回boolean值

  5. clear清空集合,返回undefined

        //声明Map
let m = new Map();
//添加元素
m.set('name', '张三');
m.set('change', function () {
console.log("多吃一碗饭");
});
let key = {
school: 'abc'
}
m.set(key, ['a', 'b', 'c']);
console.log(m)
//size
console.log(m.size);
//删除
m.delete('name');
console.log(m);
//获取
console.log(m.get(key));

image-20211023205732284

image-20211023210140185

//也可以用for of遍历
//声明Map
let m = new Map();
//添加元素
m.set('name', '张三');
m.set('change', function () {
console.log("多吃一碗饭");
});
let key = {
school: 'abc'
}
m.set(key, ['a', 'b', 'c']);
for (let i of m) {
console.log(i);
}

image-20211023210445253

class类

ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

  1. class声明类

  2. constructor定义构造函数初始化

  3. extends继承父类

  4. super调用父级构造方法

  5. static定义静态方法和属性

  6. 父类方法可以重写

        class Phone {
//构造方法名字不能修改
constructor(brand, price) {
this.brand = brand;
this.price = price;
}
//方法必须使用该语法
call() {
console.log("我可以打电话!");
}
}
let HuaWei = new Phone('华为', 1999);
console.log(HuaWei);

image-20211023211533682

static静态方法

static定义的属性属于class

        class Phone {
//静态属性
static name = '手机';
static change() {
console.log("我可以改变世界");
}
}
let HuaWei = new Phone();
console.log(HuaWei.name);
console.log(Phone.name);

image-20211023212223834

class的继承extends

        class Phone {
//构造方法名字不能修改
constructor(brand, price) {
this.brand = brand;
this.price = price;
}
//方法必须使用该语法
call() {
console.log("我可以打电话!");
}
}
class smartPhone extends Phone {
constructor(brand, price, color, size) {
super(brand, price)
this.color = color;
this.size = size;
}
photo() {
console.log("我可以拍照!");
}
playGame() {
console.log("我可以玩游戏!");
}
}
let oppo = new smartPhone('oppo', 1000, '红色', '5inch');
console.log(oppo);

image-20211023213449222

子类对父类进行重写

        class Phone {
           //构造方法名字不能修改
           constructor(brand, price) {
               this.brand = brand;
               this.price = price;
          }
           //方法必须使用该语法
           call() {
               console.log("我可以打电话!");
          }
      }
       class smartPhone extends Phone {
           constructor(brand, price, color, size) {
               super(brand, price)
               this.color = color;
               this.size = size;
          }
           //子类对父类进行重写
           call() {
               console.log("我可以视频通话!")
          }
           photo() {
               console.log("我可以拍照!");
          }
           playGame() {
               console.log("我可以玩游戏!");
          }
      }
       let oppo = new smartPhone('oppo', 1000, '红色', '5inch');
       console.log(oppo);

image-20211023213956049

get和set

对属性进行绑定

        class Phone {
           get price() {
               console.log("价格属性被读取了");
               return "iloveyou"
          }
           set price(newVal) {
               console.log("价格被修改了");
          }
      }
       let oppo = new Phone();
       console.log(oppo.price);
       console.log("-----------------------------------");
       oppo.price = 'free'

 

image-20211023214829119

数值扩充

  1. Number.EPSILON是JavaScript表示的最小精度 EPSILON属性的值接近于2.2204460492503130808472633361816E-16 如果两个数相减小于这个数那么这个两个数相等

    //可以进行浮点数的比较
    function equal(a,b){
       if(Math.abs(a-b)<Number.ESPSILON){
           return ture;
      }else{
           return false;
      }
    }
    console.log(0.1+0.2===0.3);
    console.log(equal(0.1+0.2,0.3));

     

  2. 二进制和八进制

    二进制0b开头

    八进制0o开头

    十六进制0x开头

  3. Number.isFinite检测一个数值是否为有限数

  4. Number.isNaN检测一个数值是否为NaN

  5. Number. parseInt Number.parseFloat字符串转整数

  6. Number.isInteger判断一个数是否为整数

  7. Math.trunc将数字的小数部分抹掉

  8. Math.sign判断一个数到底为正数负数还是零

模块化

模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来

模块化的好处

  1. 防止命名冲突

  2. 代码复用

  3. 高维护性

ES6模块化语法

模块功能主要有两个命令构成:export和import。

export命令用于规定模块的对外接口

import命令用于输入其他模块提供的功能

使用ES6模块化

为什么要在服务器端上部署:因为nodeJs支持commonJS规范是后端语言,前端JS不支持commonJS规范

        //1.通用的导入方式
import * as m1 from "./js/m1.js";
       console.log(school);
       import * as m2 from "./js/m2.js";
       console.log(m2);
import * as m3 from "./js/m3.js";
console.log(m3);
//像调用default中的属性需要
m3.default.change();
//2.解构赋值形式
import { school, teach } from "./js/m1.js";
       console.log(school);
       console.log(teach);
       import { school as keshi, eat } from "./js/m2.js"
       console.log(keshi);
       console.log(teach);
       import { default as m3 } from "./js/m3.js";
       console.log(m3)
//简便形式
import m3 from "./js/m3.js";
console.log(m3);//运行结果与上面一样

 

//m1.js
export let school = '科师';
export function teach() {
   console.log("我们可以教你文章");
}
//m2.js
let school = 'keshi';
function eat() {
   console.log('食堂');
}
export { school, eat };
//m3.js
//默认暴露,export default可以是任意类型,可以是对象,数字符串
export default {
   school:'keshi';
   change:function(){
       
       console.log("我们可以改变世界");
  }
}

image-20211024141431768

解构赋值形式

image-20211024142858183

 

posted on 2021-10-21 10:44  新手咋地  阅读(166)  评论(0)    收藏  举报