ES5-11


1.ES6+ 简介

ECMAScript 6, 简称 ES6。 于2015年6月正式发布,是JavaScript语言的下一代标准。

1.1ECMAScript 和 JavaScript

  • ECMA(European Computer Manufactures Association )是欧洲计算机标准化协会的简称。
  • 1996年JavaScript的创造者 Netspace(网景公司),将JavaScript提交给ECMA进行管理,希望JavScript能够成为国际标准。
  • 1997年 ECMA 发布 262 号标准文件,规定了浏览器脚本语言的标准,将其命名为 ECMAScript。
  • ECMAScript是JavaScript的规格,JavaScript是ECMAScript的实现。另外,以ECAMScript为规格的语言还有 JScript 和 ActionScript。

1.2 ES6 和 ES2015

  • ES6的第一个版本在2015年6发布,正式名称是《ECMAScript 2015标准》,故也称之为 ES2015。
  • 此后,每年的6月都会发布新的版本,2016年6月发布了 ES2016,也被称之为 ES7;2017年6月发布了ES2017,也被称之为ES8。
  • ES6 在原先的 ES5 基础上加入了大量的新特性,但是此后的版本只是进行了小幅修订,所有有时候我们使用 ES6 来代指 ES6 以及之后的版本
  • 从ES6开始,每年发布一个版本,版本号比年份最后一位大1

1.3 ECMAScript 时间节点

  • ECMAScript 1(1997 年 6 月):规范第一版。
  • ECMAScript 2(1998 年 6 月):为了同步 ISO 标准,引入了一些小更新。
  • ECMAScript 3(1999 年 12 月): 3.0 版本是一个巨大的成功,得到了广泛的支持,奠定了JavaScript的基础,我们一开始学习的JavaScript 其实就是3.0版本。
  • ECMAScript 4(2008 年 7 月废除):本来是一次大规模升级(静态类型、模块、命名空间等),但跨度过大,出现了分歧,最终没能推广使用。
  • ECMAScript 5(2009 年 12 月):变化不大,加了一些标准库特性和严格模式。
  • ECMAScript 5.1(2011 年 6 月):又一次小更新,为了同步 ISO 标准。
  • ECMAScript 6(2015 年 6 月):一大波更新,实现了当年 ES4 的许多设想,并正式改为按年份命名规范版本。
  • ECMAScript 2016(2016 年 6 月):第一个年度版本,与 ES6 相比,发布周期较短,新特性也相对少些。
  • ECMAScript 2017(2017 年 6 月):第二个年度版本。
  • 以后的 ECMAScript 版本(ES2018、ES2019、ES2020等)都在 6 月正式获准生效。

1.4 ECMAScript 语法标准制定流程

任何人都可以向标准委员会(又称 TC39 委员会)提案,要求修改语言标准。一种新的语法从提案到变成正式标准,需要经历五个阶段:

  • Stage 0 - Strawman(展示阶段)
  • Stage 1 - Proposal(征求意见阶段)
  • Stage 2 - Draft(草案阶段)
  • Stage 3 - Candidate(候选人阶段)
  • Stage 4 - Finished(定案阶段)

只要进入第 4 阶段就已经算是标准特性了,会在下一个 6 月正式纳入标准

ECMAScript 当前的所有提案,可以在 TC39 的官方网站 GitHub.com/tc39/ecma262 查看。

1.5 ES6 的兼容性问题

  • http://kangax.github.io/es5-compat-table/es6/ 可以查看各大平台对ES6的支持情况。
  • 目前最新版的各大浏览器,尤其是 Chrome 已经能够支持大部分ES6的语法。
  • node.js 也支持 ES6 大部分的语法, http://node.green/ 上可以看到node对ES6的支持情况
  • 可以使用 ==Babel== 转码器将 ES6 代码转换成浏览器兼容性更好的 ES5 代码。

1.6 参考网站

1). ES5 :

http://www.zhangxinxu.com/wordpress/2012/01/introducing-ecmascript-5-1/

http://www.ibm.com/developerworks/cn/web/wa-ecma26

2). ES6

http://es6.ruanyifeng.com/

3). ES7

http://www.w3ctech.com/topic/1614

http://www.ecma-international.org/ecma-262/6.0

https://es6.ruanyifeng.com/

2.ES5知识点

2.1 严格模式

2.1.1 什么是严格模式?

  1. 除了正常运行模式(混杂模式),ES5添加了第二种运行模式:”严格模式”(strict mode)。

  2. 顾名思义,这种模式使得Javascript在更严格的语法条件下运行

2.1.2 严格模式的目的/作用?

  1. 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为

  2. 消除代码运行的一些不安全之处,保证代码运行的安全

  3. 为未来新版本的Javascript做好铺垫

2.1.3 如何使用严格模式?

  1. 在全局或函数的第一条语句定义为: ‘use strict’;

  2. 如果浏览器不支持, 只解析为一条简单的语句, 没有任何副作用

// 全局使用严格模式
'use strict';
girl = '迪丽热巴';

// 函数中使用严格模式
function main(){
	'use strict';
	boy = '吴亦凡';
}
main();

2.1.4 语法和行为改变

  1. 必须用var声明变量

  2. 创建eval作用域

  3. 禁止this指向window

  4. 对象不能有重名的属性

  5. 函数不能有重名的形参

2.2 json对象

2.2.1 什么是json字符串

  1. ‘abc’和‘{“username”: “kobe”}’ 这两个都是json字符串对吗?

  2. Json字符串是一种数据格式,通常用来前后端进行数据传递

2.2.2 json字符串同原生JS对象/数组相互转换

  1. JSON.stringify(obj/arr)

    js对象(数组)转换为json对象(数组)

    也就是字符串化 前端往后端传输数据,必须先把数据封装为对象
    然后在转换为json传,是为把前端json对象或者是对象数组转换为json字符串

  2. JSON.parse(json)

    json对象(数组)转换为js对象(数组)

    后端传过来的json数据我要转换为前端的对象或者对象数组进行处理

2.2.3 思考:json字符串中为什么只有json对象/json数组?

  1. json对象/数组 作为数据进行传递后通常需要转换为原生JS对象/数组才能去使用

  2. json对象/数组就是为了与原生的JS对象/数组相互转换的

原生的字符串无法转换为JS对象/数组,所以不能算为json字符串,毫无意义

2.3 Object扩展

2.3.1 Object.create(prototype, [descriptors])

Object.create 方法可以以指定对象为原型创建新的对象,同时可以为新的对象设置属性, 并对属性进行描述

* value : 指定值
* writable : 标识当前属性值是否是可修改的, 默认为 false
* configurable:标识当前属性是否可以被删除 默认为 false
* enumerable:标识当前属性是否能用for in 枚举 默认为 false
* get:   当获取当前属性时的回调函数
* set:   当设置当前属性时
//创建一个汽车的对象
var car = {
    name : '汽车',
    run: function(){
        console.log('我可以行驶!!');
    }
};

//以 car 为原型对象创建新对象
var aodi = Object.create(car, {
    brand: {
        value: '奥迪',
        writable: false,         //是否可修改
        configurable: false,     //是否可以删除
        enumerable: true         //是否可以使用 for...in 遍历
    },
    color: {
        value : '黑色',
        wriable: false,
        configurable: false,
        enumerable: true
    }
});

2.3.2 Object.defineProperties(object, descriptors)

直接在一个对象上定义新的属性或修改现有属性,并返回该对象。

  • object 要操作的对象
  • descriptors 属性描述
    • get 作为该属性的 getter 函数,如果没有 getter 则为undefined。函数返回值将被用作属性的值。
    • set 作为属性的 setter 函数,如果没有 setter 则为undefined。函数将仅接受参数赋值给该属性的新值。
// 定义对象
var star = {
    firstName: '刘',
    lastName : '德华'
};

// 为 star 定义额外的属性
Object.defineProperties(star, {
    fullName: {
        get: function(){
            return this.firstName + this.lastName;
        },
        set: function(name){
            var res = name.split('-');
            this.firstName = res[0];
            this.lastName = res[1];
        }
    }
});

// 修改 fullName 属性值
star.fullName = '张-学友';

// 打印属性
console.log(star.fullName);

2.3.3 对象本身的get和set方法

var obj = {
    firstName : 'kobe',
    lastName : 'bryant',
    get fullName(){
        return this.firstName + ' ' + this.lastName
    },
    set fullName(data){
        var names = data.split(' ');
        this.firstName = names[0];
        this.lastName = names[1];
    }
};
console.log(obj.fullName);
obj.fullName = 'curry stephen';
console.log(obj.fullName);

2.4 Array扩展

1. Array.prototype.indexOf(value) : 得到值在数组中的第一个下标
2. Array.prototype.lastIndexOf(value) : 得到值在数组中的最后一个下标
3. Array.prototype.forEach(function(item, index){}) : 遍历数组
4. Array.prototype.map(function(item, index){}) : 遍历数组返回一个新的数组,返回加工之后的值
5. Array.prototype.filter(function(item, index){}) : 遍历过滤出一个新的子数组, 返回条件为true的值

2.5 call、apply 和 bind

* call 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数
* apply 方法调用一个具有给定 this 值的函数,以及作为一个数组(或类似数组对象)提供的参数
* bind 同 call 相似,不过该方法会返回一个新的函数,而不会立即执行
function main(){
    console.log(this);
}
/*1. 直接调用函数*/
main();										//  window
/*2. 创建一个对象*/
var company = {name: '尚硅谷', age: 10};
/*使用这个对象调用 main 方法*/
main.call(company);							// company
main.apply(company);						// company
/*bind 修改 this 的值,返回一个新的函数*/
var fn = main.bind(company);
fn();										// company

3.ES2015(ES6)

3.1 ES6必备

3.1.1 定义变量/常量

使用var关键字声明变量的弊端:

1、var声明的变量有预解析,造成 逻辑混乱,可以先使用,后声明

2、var可以重复定义同一个变量,逻辑错误,第二次应该是修改变量,而不是定义

3、var用在for循环条件中,造成for 循环的污染的问题

4、var 声明的变量没有块级作用域(ES5中的作用域:全局和局部)

// 1、var声明的变量有解析,造成 逻辑混乱,可以先使用,后声明
// console.log(a);
// var a = 10;

// 2、var可以重复定义同一个变量,逻辑错误,第二次应该是修改变量,而不是定义
// var a = 10;
// var a = 30;
// console.log(a);

// 3、var用在for循环条件中,造成for 循环的污染的问题
// for(var i=0; i<10; i++){
//     console.log(i);
// }
// console.log("=====================");
// console.log(i);

// 4、var 声明的变量没有块级作用域(ES5中的作用域:全局和局部)
// {
//     var b = 200;
// }
// console.log(b);
  1. let定义变量

var关键字其实是有不少的问题的,比如var的变量会提升,提升后会造成一定的混乱,你可以在变量声明之前使用这个变量,而此时你对于这个变量是否有值是不确定的,所以var的变量提升后其实是有一定的问题的。

因此es6中为了统一并提高代码的安全性,引入了let关键字来代替var声明变量

* let声明的变量不会提升,必须要在声明后才能使用  会取消掉预解析
console.log(data) // Uncaught ReferenceError: Cannot access 'data' before initialization
let data = 10;

* let声明的变量不能重复声明
let data = 10;
let data = 20; // Uncaught SyntaxError: Identifier 'data' has already been declared

* let声明的变量存在块级作用域
if(false){
  let data = 10;
}
console.log(data) // Uncaught ReferenceError: data is not defined

2) const定义常量

1、理解:什么是常量? 常量通常被认为长时间保持一个状态,不应该被修改
2、在ES5中定义常量也是用var但没有真正意义上实现常量的意义,可以被修改
3、定义常量const,定义的常量不能被修改
    1)	声明一定要赋初始值
    2)	不允许重复声明
    3)	值不允许修改
    4)	块儿级作用域
注意: 对象属性修改和数组元素变化不会出发const错误
应用场景:声明对象类型使用const,非对象类型声明选择let

const 常量名 =;
例如:
const data = 100;

如果我们声明了常量不赋值:
const data; // Uncaught SyntaxError: Missing initializer in const declaration

const arr = [1,2,3];
arr[0]=60;
小结:
1. 当一个数据不会变化的时候,我们就要使用const来声明
2. 常量的恒定不变,只是相对于值类型来说的,引用类型还是能改变其属性

3.1.2 变量的解构赋值

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

  1. 理解(解构赋值四字一分为二):

- 解构:解析目标对象的结构

- 赋值:解析结构后获取数据

  1. 对象的解构赋值
let {n, a} = {n:'tom', a:12}
  1. 数组的解构赋值
let [a,b] = [1, 'atguigu'];
  1. 用途

多用在给多个形参赋值

//数组的解构赋值
const arr = ['张学友', '刘德华', '黎明', '郭富城'];
let [zhang, liu, li, guo] = arr;

//对象的解构赋值
const lin = {
    name: '林志颖',
    tags: ['车手', '歌手', '小旋风', '演员']
};
let {name, tags} = lin;   //{ }中的变量名和lin的属性名一致   完全解构

//复杂解构
let wangfei = {
    name: '王菲',
    age: 18,
    songs: ['红豆', '流年', '暧昧', '传奇'],
    history: [
        {name: '窦唯'},
        {name: '李亚鹏'},
        {name: '谢霆锋'}
    ]
};
let {songs: [one, two, three], history: [first, second, third]} = wangfei;

注意:频繁使用对象方法、数组元素,就可以使用解构赋值形式

3.1.3 模板字符串

板字符串(template string)是增强版的字符串,用反引号(`)标识,特点:

  1. 字符串中可以出现换行符

  2. 可以使用 ${xxx} 形式输出变量

// 定义字符串
let str = `<ul>
        <li>沈腾</li>
        <li>玛丽</li>
        <li>魏翔</li>
        <li>艾伦</li>
    </ul>`;

// 变量拼接
let star = '王宁';
let result = `${star}在前几年离开了开心麻花`;

注意:当遇到字符串与变量拼接的情况使用模板字符串

3.1.4 简化的对象写法

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

  1. 省略同名的属性值

  2. 省略方法的function

当我们在以前定义对象字面量的时候,如果有如下代码

let name = '狗蛋';
age = 12;
gender = '男';

let obj = {
  name : name,
  age : age,
  gender : gender
}

此时我们发现obj的属性名和变量是同样的,可以在es6中简化为:

let obj = {name,age,gender}

也就是说,如果一个对象的属性名和外面的一个变量名同名,可以直接交变量名作为属性名,并会自动地把变量的值作为属性的值

let name = '尚硅谷';
let slogon = '永远追求行业更高标准';
let improve = function () {
    console.log('可以提高你的技能');
}

//属性和方法简写
let atguigu = {
    name,
    slogon,
    improve,
    change() {
        console.log('可以改变你')
    }
};

注意:对象简写形式简化了代码,所以以后用简写就对了

3.1.5 箭头函数

  1. 作用:定义匿名函数表达式

  2. 场景:多用来定义回调函数

  3. 特点:

    * 语法简洁

    * 没有自己的this,定义时候所处的对象就是它的this

    * 扩展理解:看外层有没有函数,如果有则当前箭头函数的this同外层函数的this一样,如果没有则this指向window

4.语法:

* 没有参数: () => console.log('xxxx')
* 一个参数: i => i+2
* 大于一个参数: (i,j) => i+j
* 函数体不用大括号: 默认返回结果
* 函数体如果有多个语句, 需要用{}包围,若有需要返回的内容,需要手动返回
固定语法:(参数) => { 函数体 }

// function func(){
//     console.log("hello");
// }

// 以上代码使用箭头函数书写为:
let func = () => {
    console.log("hello");
};
func();

/**
 * 1. 通用写法
 */
let fn = (arg1, arg2, arg3) => {
    return arg1 + arg2 + arg3;
}

箭头函数的注意点:

  1. 箭头函数this指向声明时所在作用域下 this 的值

  2. 箭头函数不能作为构造函数实例化

  3. 箭头函数内没有arguments,可以使用 rest 参数代替。

  4. 如果形参只有一个,则小括号可以省略

  5. 函数体如果只有一条语句,则花括号可以省略,函数的返回值为该条语句的执行结果

/**
 * 2. 省略小括号的情况 
 */
let fn2 = num => {
    return num * 10;
};

/**
 * 3. 省略花括号的情况
 */
let fn3 = score => score * 20;

/**
 * 4. this指向声明时所在作用域中 this 的值
 */
let fn4 = () => {
    console.log(this);
}

let school = {
    name: '尚硅谷',
    getName(){
        let fn5 = () => {
            console.log(this);
        }
        fn5();
    }
};


// 无参数无返回
let func11 = () => console.log('func11');
func11();

// 无参数有返回
let func22 = () => 'func22';
console.log(func22());

// 有参数无返回
let func33 = x => console.log('func33', x);
func33(2);

// 有参数有返回
let func44 = (x, y) => {
    let sum = x + y; 
    return sum + 'func44';
};
console.log(func44(1, 2));

注意:
// 如果return的是单一个对象,则需要加上大括号和return,例如:

// let func55 = (x, y) => {a:,x b:y};    //报错
let func66 = (x, y) => {
    return { a: x, b: y };
};
console.log(func66(5, 8));

// 箭头函数不可以使用 arguments 获取参数列表,可以使用 rest 参数代替。
let func=(a,b)=>{
    console.log(a,b);
    console.log(arguments);
}
func(1,2)// 这种方式获取不到实参列表
let fun=(...value)=>console.log(value);
fun(1,2)//[ 1, 2 ]

使用场景(回调函数):
// 定时器中的回调函数
setInterval(() => {
    console.log("我用了箭头函数");
}, 1000);

// forEach中的回调函数
var arr = [22, 32, 11, 3, 5, 7, 88];
arr.forEach(item => console.log(item));

注意:箭头函数不会更改this指向,所以非常适合设置与this无关的回调,比如数组回调、定时器回调,不适合事件回调与对象方法。

  1. 全局使用(函数全局调用)指向window
  2. 对象调用指向该对象(事件中的事件源)
  3. 箭头函数没有自己的作用域,即箭头函数 this 指向其外层作用域
var name = "windowName";
var obj = {
    name: "奶茶",
    fn: function () {
        console.log(this.name);
    },
};
obj.fn();

var obj2 = {
    name: "绿茶",
    fn2: () => {
        console.log(this.name);
    },
};
obj2.fn2();

//所以创建字面量对象,不适合书写箭头函数

在dom操作中使用箭头函数

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .box{
            width: 200px;
            height: 200px;
            background-color: pink;
        }
    </style>
</head>
<body>
    <div id="odiv" class="box"></div>
    <script>
      var obj = document.getElementById("odiv");
      obj.onclick = function () {
        // setTimeout(function () {
        //   console.log(this);
        //   this.style.width = "300px"; //修改不了
        // }, 1000);

        setTimeout(() => {
          console.log(this);
          this.style.width = "300px"; //修改成功
        }, 1000);
      };
    </script>
</body>
</html>

3.1.6 点点点运算符

  1. rest(可变)参数,用来取代arguments 但比arguments灵活,只能是最后部分形参参数

rest能把参数形成一个数组

function fn(){
    console.log(arguments);// 伪数组
}

fn(10, 20, 30, 50, 60);

ES6提供了新的方法:使用rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。

/**
 * 作用与 arguments 类似
 */
function add(...args){ //rest 接收所有参数作为一个数组
    console.log(args);
}
add(1,2,3,4,5);

/**
 * rest 参数必须是最后一个形参
 */
function minus(a,b,...args){ // 把剩余的参数都交给rest
    console.log(a,b,args);
}
minus(100,1,2,3,4,5,19);

spread扩展运算符

扩展运算符(spread)也是三个点(…)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列,对数组进行解包。

spread是把数组形成逗号分割的字符串

/**
 * 展开数组
 */ 
let tfboys = ['德玛西亚之力','德玛西亚之翼','德玛西亚皇子'];
function fn(){
    console.log(arguments);
}
fn(...tfboys)


/**
 * 展开对象
 */
let skillOne = {
    q: '致命打击',
};
let skillTwo = {
    w: '勇气'
};
let skillThree = {
    e: '审判'
};
let skillFour = {
    r: '德玛西亚正义'
};

let gailun = {...skillOne, ...skillTwo,...skillThree,...skillFour};

3.1.7 形参默认值

当不传入参数的时候默认使用形参里的默认值
function Point(x = 1,y = 2) {
this.x = x;
this.y = y;
}

3.1.8 Promise对象

  1. 理解Promise对象
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了`Promise`对象。

**功能:避免了回调地狱,把异步代码改成调用起来像同步代码。**

所谓`Promise`,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

**一个 Promise 对象 有以下几种状态:**
    - pending: 初始状态,既不是成功,也不是失败状态。
    - fulfilled: 意味着操作成功完成。
    - rejected: 意味着操作失败。

**`Promise`对象有以下两个特点:**1)对象的状态不受外界影响。`Promise`对象代表一个异步操作,有三种状态:`pending`(进行中)、`fulfilled`(已成功)和`rejected`(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是`Promise`这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。`Promise`对象的状态改变,只有两种可能:从`pending`变为`fulfilled`和从`pending`变为`rejected`。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对`Promise`对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

*Promise是ES6引入的异步编程的新解决方案。语法上Promise是一个构造函数,new出来实例对象可以帮我们解决异步操作的一些问题,用来封装异步操作并可以获取其成功或失败的结果。

* Promise对象: 代表了未来某个将要发生的事件(通常是一个异步操作)

* 我们如果在进行ajax请求的时候,一个效果需要有多个请求按照一定的顺序完成,如果不使用Promise实现,做起来就容易形成回调地狱  (有了promise对象, 可以将异步操作以同步的流程表达出来, 避免了层层嵌套的回调函数(俗称'回调地狱'))

* ES6的Promise是一个构造函数, 用来生成promise实例
  1. 使用Promise对象
* 创建promise对象
  let promise = new Promise((resolve, reject) => {
    //初始化promise状态为 pending
    //执行异步操作
    if(异步操作成功) {
      resolve(value);//修改promise的状态fullfilled
    } else {
      reject(errMsg);//修改promise的状态为rejected
    }
  })
* 调用promise对象的then()
  promise.then(function(
    result => console.log(result),
    errorMsg => alert(errorMsg)
  ))
  1. 应用

使用Promise对象实现超时处理

function getNews(url) {
    //创建一个promise对象
    let promise = new Promise((resolve, reject) => {
        //初始化promise状态为pending
        //启动异步任务
        let request = new XMLHttpRequest();
        request.onreadystatechange = function () {
            if(request.readyState === 4){
                if(request.status === 200){
                    let news = request.response;
                    resolve(news);
                }else{
                    reject('请求失败了。。。');
                }
            }
        };
        request.responseType = 'json';//设置返回的数据类型
        request.open("GET", url);//规定请求的方法,创建链接
        request.send();//发送
    })
    return promise;
}

getNews('http://localhost:3000/news?id=2')
        .then((news) => {
            console.log(news);
            document.write(JSON.stringify(news));
            console.log('http://localhost:3000' + news.commentsUrl);
            return getNews('http://localhost:3000' + news.commentsUrl);
        }, (error) => {
            alert(error);
        })
        .then((comments) => {
            console.log(comments);
            document.write('<br><br><br><br><br>' + JSON.stringify(comments));
        }, (error) => {
            alert(error);
        })
  1. 如何解决多重请求(回调地狱)
let p1 = new Promise((resolve,reject)=>{
  $.ajax({
    url:'url1',
    success(res){
      resolve(res)
    },
    error(err){
      reject(err)
    }
  })
})
let p2 = new Promise((resolve,reject)=>{
  $.ajax({
    url:'url2',
    success(res){
      resolve(res)
    },
    error(err){
      reject(err)
    }
  })
})
let p3 = new Promise((resolve,reject)=>{
  $.ajax({
    url:'url3',
    success(res){
      resolve(res)
    },
    error(err){
      reject(err)
    }
  })
})

p1.then(res1=>{
  console.log('第一个请求完成')
  return p2;
}).then(res2=>{
  console.log('第二个请求完成')
  return p3
}).then(res3=>{
  console.log('第三个请求完成')
})

这样我们就使用了Promise解决了回调函数里面嵌套回调函数的问题。

小结: 我们可以通过多个promise调用then方法来把地狱回调转化为链式编程,便于后期的维护

  1. 简化多重请求的promise写法
function PromiseAjax(url){
  return new Promise((resolve,reject)=>{
    $.ajax({
      url,
      success(res){
        resolve(res)
      },
      error(err){
        reject(err)
      }
    })
  })
}

let p1 = PromiseAjax('url1')
let p2 = PromiseAjax('url2')
let p2 = PromiseAjax('url2')

3.1.9 Symbol属性

ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,是一种类似于字符串的数据类型。

前言:`ES5中对象的属性名都是字符串,容易造成重名,污染环境`
1) 什么是Symbol属性
ES6中的添加了一种原始数据类型symbol(已有的原始数据类型:String, Number, boolean, null, undefined, 对象)

2) Symbol特点
1、Symbol属性对应的值是唯一的,解决命名冲突问题
2、Symbol值不能与其他数据进行计算,包括同字符串拼串
3for in, for of遍历时不会遍历symbol属性,但是可以使用Reflect.ownkeys来获取对象的所有健名

3) Symbol应用
1. 调用Symbol函数得到symbol值
	*let symbol = Symbol();  let obj = {};  obj[symbol] = 'hello';*`` 
2. 传参标识
	*let symbol = Symbol('one');  let symbol2 = Symbol('two');  console.log(symbol);// 		Symbol('one')  console.log(symbol2);// Symbol('two')*
3. 内置Symbol值
* 除了定义自己使用的Symbol值以外,ES6还提供了11个内置的Symbol值,指向语言内部使用的方法。
	* Symbol.iterator,对象的Symbol.iterator属性,指向该对象的默认遍历器方法(后边讲)
//创建 Symbol
let s1 = Symbol();
console.log(s1, typeof s1);

//添加标识的 Symbol
let s2 = Symbol('尚硅谷');
let s2_2 = Symbol('尚硅谷');
console.log(s2 === s2_2);

//使用 Symbol for 定义
let s3 = Symbol.for('尚硅谷');
let s3_2 = Symbol.for('尚硅谷');
console.log(s3 === s3_2);

注: Symbol类型唯一合理的用法是用变量存储 symbol的值,然后使用存储的值创建对象属性

3.1.10 iterator遍历器

1. 什么是iterator?

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

2. Iterator接口作用

1、为各种数据结构,提供一个统一的、简便的访问接口;
2、使得数据结构的成员能够按某种次序排列
3ES6创造了一种新的遍历命令for...of循环,`Iterator接口主要供for...of消费`原生具备Iterator接口的数据(可用for of遍历)
    a)	Array
    b)	Arguments
    c)	Set
    d)	Map
    e)	String
    f)	TypedArray
    g)	NodeList

3. Iterator接口工作原理

1. 创建一个指针对象,指向数据结构的起始位置。
2. 第一次调用next方法,指针自动指向数据结构的第一个成员
3. 接下来不断调用next方法,指针会一直往后移动,直到指向最后一个成员
4.每调用next方法返回的是一个包含value和done的对象,{value: 当前成员的值,done: 布尔值}
  4.1 value表示当前成员的值,done对应的布尔值表示当前的数据的结构是否遍历结束。
  4.2 当遍历结束的时候返回的value值是undefined,done值为false

4. 原生具备iterator接口的数据类型

1、Array
2、arguments
3、set容器
4、map容器
5、String

5. ES6方法和iterator接口的关系

1. 使用解构赋值以及...三点运算符时会调用iterator接口
2. Generator函数的yeild语句会调用iterator接口(后边讲)

6. 自定义iterator接口(遍历器对象)

function mockIterator(arr) {
  let nextIndex = 0; // 记录访问数据结构的位置
  return {
    next: function () {// 遍历器对象
      return nextIndex<arr.length?{value: arr[nextIndex++], done: false}:{value: undefined, done: true} // 动态返回遍历的结果数据
    }
  }
}

let arr = [1,2,3,4,5];
let iteratorObj = mockIterator(arr);
console.log(iteratorObj.next());
console.log(iteratorObj.next());
console.log(iteratorObj.next());

3.1.11 Generator函数

1、 什么是Generator函数?

1、ES6提供的解决异步编程的方案之一
2、Generator函数是一个状态机,内部封装了不同状态的数据,
3、用来生成遍历器对象(iterator接口)
4、可暂停函数(惰性求值), yield可暂停,next方法可启动。每次返回的是yield后的表达式结果

2、 Generator函数特点

1function 与函数名之间有一个星号

2、内部用yield表达式来定义不同的状态
例如:
  function* generatorExample(){
    let result = yield 'hello';  // 状态值为hello
    yield 'generator'; // 状态值为generator
  }
3、generator函数返回的是指针对象(3.1.10章节里iterator),而不会执行函数内部逻辑
4、调用next方法函数内部逻辑开始执行,遇到yield表达式停止,返回{value: yield后的表达式结果/undefined, done: false/true}
5、再次调用next方法会从上一次停止时的yield处开始,直到最后
6yield语句返回结果通常为undefined, 当调用next方法时传参内容会作为启动时yield语句的返回值。

function* generatorTest() {
  console.log('函数开始执行');
  yield 'hello';
  console.log('函数暂停后再次启动');
  yield 'generator';
}
// 生成遍历器对象
let Gt = generatorTest();
// 执行函数,遇到yield后即暂停
console.log(Gt); // 遍历器对象
let result = Gt.next(); // 函数执行,遇到yield暂停
console.log(result); // {value: "hello", done: false}
result = Gt.next(); // 函数再次启动
console.log(result); // {value: 'generator', done: false}
result = Gt.next();
console.log(result); // {value: undefined, done: true}表示函数内部状态已经遍历完毕

3、人为给对象添加iterator接口(可用for of遍历)

let myIterable = {};
myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 4;
};
for(let i of myIterable){
  console.log(i);
}
let obj = [...myIterable];
console.log(obj);

4、案例练习

* 需求:
* 1、发送ajax请求获取新闻内容
* 2、新闻内容获取成功后再次发送请求,获取对应的新闻评论内容
* 3、新闻内容获取失败则不需要再次发送请求。

function* sendXml() {
  // url为next传参进来的数据
 let url = yield getNews('http://localhost:3000/news?newsId=2');
  yield getNews(url);
}
function getNews(url) {
  $.get(url, function (data) {
    console.log(data);
    let commentsUrl = data.commentsUrl;
    let url = 'http://localhost:3000' + commentsUrl;
    // 当获取新闻内容成功,发送请求获取对应的评论内容
    // 调用next传参会作为上次暂停是yield的返回值
    sx.next(url);
  })
}


let sx = sendXml();
// 发送请求获取新闻内容
sx.next();

3.1.12 async函数

贴心小提示:async函数来自ES7

1、什么是async函数

  1. Generator函数的语法糖
2. 真正意义上去解决异步回调的问题,同步流程表达异步操作

2、async语法

*async function foo(){   await* *异步操作**;   await* *异步操作;**  }*

3、async函数特点

1、不需要像Generator去调用next方法,遇到await等待,当前的异步操作完成就往下执行
2、返回的总是Promise对象,可以用then方法进行下一步操作
3、async取代Generator函数的星号*,await取代Generator的yield
4、语意上更为明确,使用简单,经临床验证,暂时没有任何副作用

4、案例练习

async function sendXml(url) {
  return new Promise((resolve, reject) => {
    $.ajax({
      url,
      type: 'GET',
      success: data =>  resolve(data),
      error: error => reject(error)
    })
  })
}

async function getNews(url) {
  let result = await sendXml(url);
  let result2 = await sendXml(url);
  console.log(result, result2);
}
getNews('http://localhost:3000/news?id=2')

3.1.13 class类

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

1)	class声明类
2)	constructor定义构造函数初始化
3)	extends继承父类
4)	super调用父级构造方法
5)	static定义静态方法和属性
6)	父类方法可以重写


//父类
class Phone {
    //构造方法
    constructor(brand, color, price) {
        this.brand = brand;
        this.color = color;
        this.price = price;
    }
    //对象方法
    call() {
        console.log('我可以打电话!!!')
    }
}

//子类
class SmartPhone extends Phone {

    constructor(brand, color, price, screen, pixel) {
        //super是父类的构造函数
        super(brand, color, price);
        this.screen = screen;
        this.pixel = pixel;
    }
    //子类方法
    photo(){
        console.log('我可以拍照!!');
    }

    playGame(){
        console.log('我可以玩游戏!!');
    }

    //方法重写
    call(){
        console.log('我可以进行视频通话!!');
    }

    //静态方法
    static run(){
        console.log('我可以运行程序')
    }

    static connect(){
        console.log('我可以建立连接')
    }
}

//实例化对象
const Nokia = new Phone('诺基亚', '灰色', 230);
const iPhone6s = new SmartPhone('苹果', '白色', 6088, '4.7inch','500w');

//调用子类方法
iPhone6s.playGame();
//调用重写方法
iPhone6s.call();
//调用静态方法
SmartPhone.run();

3.1.14 Module模块化

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

模块化的好处

​ 可以防止命名冲突、代码复用、高维护性

模块化规范产品

​ es6之前的模块化规范有:

​ 1.commonjs=>Nodejs、Browserify

​ 2.AMD => requirejs

​ 3.CMD => SeaJs

Es6模块化的语法

​ 模块化功能主要是由两个命令构成:export和import

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

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

3.2 ES6其他语法

3.2.1 字符串扩展

1. includes(str) : 判断是否包含指定的字符串
2. startsWith(str) : 判断是否以指定字符串开头
3. endsWith(str) : 判断是否以指定字符串结尾
4. repeat(count) : 重复指定次数

//startsWith(str) : 判断是否以指定字符串开头
console.log(str.startsWith('a'));//true
console.log(str.startsWith('d'));//false
//endsWith(str) : 判断是否以指定字符串结尾
console.log(str.endsWith('g'));//true
console.log(str.endsWith('d'));//false
//repeat(count) : 重复指定次数a
console.log(str.repeat(5));

3.2.2 数值扩展

1. 二进制与八进制数值表示法: 二进制用0b, 八进制用0o
2. Number.isFinite(i) : 判断是否是有限大的数
3. Number.isNaN(i) : 判断是否是NaN
4. Number.isInteger(i) : 判断是否是整数
5. Number.parseInt(str) : 将字符串转换为对应的数值
6. Math.trunc(i) : 直接去除小数部分

//Number.isFinite(i) : 判断是否是有限大的数
console.log(Number.isFinite(NaN));//false
console.log(Number.isFinite(5));//true
//Number.isNaN(i) : 判断是否是NaN
console.log(Number.isNaN(NaN));//true
console.log(Number.isNaN(5));//falsse

//Number.isInteger(i) : 判断是否是整数
console.log(Number.isInteger(5.23));//false
console.log(Number.isInteger(5.0));//true
console.log(Number.isInteger(5));//true

//Number.parseInt(str) : 将字符串转换为对应的数值
console.log(Number.parseInt('123abc'));//123
console.log(Number.parseInt('a123abc'));//NaN

// Math.trunc(i) : 直接去除小数部分
console.log(Math.trunc(13.123));//13

3.2.3 数组扩展

1. Array.from(v):将伪数组对象或可遍历对象转换为真数组
2. Array.of(v1, v2, v3) : 将一系列值转换成数组
3. find(function(value, index, arr){return true}) : 找出第一个满足条件返回true的元素
4. findIndex(function(value, index, arr){return true}) : 找出第一个满足条件返回true的元素下标

let btns = document.getElementsByTagName('button');
console.log(btns.length);//3
Array.from(btns).forEach(function (item, index) {
    console.log(item, index);
});
//Array.of(v1, v2, v3) : 将一系列值转换成数组
let arr = Array.of(1, 'abc', true);
console.log(arr);

3.2.4 对象扩展

1. Object.is(v1, v2)
  * 判断2个数据是否完全相等

2. Object.assign(target, source1, source2..)
  * 将源对象的属性复制到目标对象上

3. 直接操作 __proto__ 属性
  let obj2 = {};
  obj2.__proto__ = obj1;
  
//Object.assign(target, source1, source2..)
let obj = {name : 'kobe', age : 39, c: {d: 2}};
let obj1 = {};
Object.assign(obj1, obj);
console.log(obj1, obj1.name);

//直接操作 __proto__ 属性
let obj3 = {name : 'anverson', age : 41};
let obj4 = {};
obj4.__proto__ = obj3;
console.log(obj4, obj4.name, obj4.age);

3.2.5 深度克隆(浅拷贝)

1. 对象类型:

1、数据分为基本的数据类型(String, Number, boolean, Null, Undefined)和对象数据类型
2、基本数据类型:
  特点: 存储的是该对象的实际数据
3、对象数据类型:
  特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里

2. 复制数据

2.1基本数据类型存放的就是实际的数据,可直接复制
let number2 = 2;
let number1 = number2; //对于基本数据类型来说,赋值也就是拷贝,基本数据类型拷贝都叫深拷贝

let arr = [1,2,3];
let arr1 = arr;
consloe.log(arr); //浅拷贝,修改拷贝后的值,会影响拷贝前的值,那么此拷贝就是浅拷贝
2.2 对象/数组
区别: 浅拷贝/深度拷贝
判断: 拷贝是否产生了新的数据还是拷贝的是数据的引用

如果复制出来新的数据,发生了数据改变,对原数据有影响,这个就叫浅拷贝
如果复制出来的数据,对他进行任何修改,跟原数据不会产生影响,这个就叫深拷贝

知识点:对象数据存放的是对象在栈内存的引用,直接复制的是对象的引用
   let obj = {username: 'kobe'}
   let obj1 = obj; // obj1 复制了obj在栈内存的引用

3. 常用拷贝技术

1). arr.concat(): 数组浅拷贝
2). arr.slice():  数组浅拷贝
3). JSON.parse(JSON.stringify(arr/obj)): 数组或对象深拷贝, 但不能处理函数数据
4). Object.assign(target, source)

4. 实现深度克隆

// 深度克隆(复制)

function getObjClass(obj) {
  let result = Object.prototype.toString.call(obj).slice(8, -1);
  if(result === 'Null'){
    return 'Null';
  }else if(result === 'Undefined'){
    return 'Undefined';
  }else {
    return result;
  }
}
// for in 遍历数组的时候遍历的是下标
let testArr = [1,2,3,4];
for(let i in testArr){
  console.log(i); // 对应的下标索引
}

// 深度克隆
function deepClone(obj) {
  let result, objClass = getObjClass(obj);
  if(objClass === 'Object'){
    result = {};
  }else if(objClass === 'Array'){
    result = [];
  }else {
    return obj; // 如果是其他数据类型不复制,直接将数据返回
  }
  // 遍历目标对象
  for(let key in obj){
    let value = obj[key];
    if(getObjClass(value) === "Object" || 'Array'){
      result[key] = deepClone(value);
    }else {
      result[key] = obj[key];
    }
  }
  return result;
}


let obj3 = {username: 'kobe',age: 39, sex: {option1: '男', option2: '女'}};
let obj4 = deepClone(obj3);
console.log(obj4);
obj4.sex.option1 = '不男不女'; // 修改复制后的对象不会影响原对象
console.log(obj4, obj3);

3.2.6 Set和Map容器

ES6 提供了新的数据结构 Set(集合)。它类似于数组,但成员的值都是唯一的(无序不可重复的多个value的集合体),集合实现了iterator接口,所以可以使用『扩展运算符』和『forof…』进行遍历,集合的属性和方法:
1. Set容器 : 
1)	size 	返回集合的元素个数
2)	add		增加一个新元素,返回当前集合
3)	delete	删除元素,返回boolean 值
4)	has		检测集合中是否包含某个元素,返回boolean值
5)	clear	清空集合,返回undefined

//创建一个空集合
let s = new Set();
//创建一个非空集合
let s1 = new Set([1,2,3,1,2,3]);

//集合属性与方法
//返回集合的元素个数
console.log(s1.size);
//添加新元素
console.log(s1.add(4));
//删除元素
console.log(s1.delete(1));
//检测是否存在某个值
console.log(s1.has(2));
//清空集合
console.log(s1.clear());


ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合(无序的 key不重复的多个key-value的集合体)。但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map也实现了iterator接口,所以可以使用『扩展运算符』和『forof…』进行遍历。Map的属性和方法:
1)	size 	返回Map的元素个数
2)	set		增加一个新元素,返回当前Map
3)	get		返回键名对象的键值
4)	has		检测Map中是否包含某个元素,返回boolean值
5)	clear	清空集合,返回undefined

//创建一个空 map
let m = new Map();
//创建一个非空 map
let m2 = new Map([
    ['name','尚硅谷'],
    ['slogon','不断提高行业标准']
]);

//属性和方法
//获取映射元素的个数
console.log(m2.size);
//添加映射值
console.log(m2.set('age', 6));
//获取映射值
console.log(m2.get('age'));
//检测是否有该映射
console.log(m2.has('age'));
//清除
console.log(m2.clear());

3.2.7 for of循环

1. 遍历数组
2. 遍历Set
3. 遍历Map
4. 遍历字符串
5. 遍历伪数组

let arr = [1,2,3,4,5];
for(let num of arr){
    console.log(num);
}
let set = new Set([1,2,3,4,5]);
for(let num of set){
    console.log(num);
}
let str = 'abcdefg';
for(let num of str){
    console.log(num);
}
let btns = document.getElementsByTagName('button');
for(let btn of btns){
    console.log(btn.innerHTML);
}


for是最原始的循环  用来遍历数组
foreach是数组的方法,遍历数组简单粗暴,但是不能breakcontinue
for in 是用来遍历对象属性的,效率最慢,因为他不但要遍历对象本身也会去遍历对象原型里面的属性
for of  可以遍历数组、伪数组、set、map、字符串等等,遍历的就是值

4.ES7知识点

贴心小提示 ES7大部分语法也还是草案,只有少部分正式发布使用

1 新增指数运算符

2 ** 2 // 4
2 ** 3 // 8
1. 指数运算符(): **
2. Array.prototype.includes(value) : 判断数组中是否包含指定value
console.log(3 ** 3);//27
let arr = [1,2,3,4, 'abc'];
console.log(arr.includes(2));//true
console.log(arr.includes(5));//false

2 数组实例新增方法

数组实例的includes()方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的 includes() 方法类似。

[1, 2, 3].includes(2)     // true
[1, 2, 3].includes(4)     // false
[1, 2, NaN].includes(NaN) // true

[1, 2, 3].includes(3, 3);  // false
[1, 2, 3].includes(3, -1); // true

ES2017(ES8)新增特性

1 字符串实例新增方法

新增了补全长度方法 padStart() 和 padEnd()。

'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'

'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'

2 函数新增特性

ES2017 允许函数的最后一个参数有尾逗号(trailing comma)。

此前,函数定义和调用时,都不允许最后一个参数后面出现逗号。

function clownsEverywhere(param1,param2,) { 
  /* ... */ 
}

clownsEverywhere('foo','bar',);

3 Object 构造函数本身新增方法

3.1 Object.valuee() 和 Object.entires() 方法

ES2017引入,Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。

ES2017引入,Object.entries方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。

3.2 Object.fromEntries() 方法

Object.fromEntries()方法是Object.entries()的逆操作,用于将一个键值对数组转为对象。

Object.fromEntries([  ['foo', 'bar'],  ['baz', 42]])// { foo: "bar", baz: 42 }

3.3 Object.getOwnPropertyDescriptors() 方法

ES5 的Object.getOwnPropertyDescriptor()方法会返回某个对象属性的描述对象(descriptor)。

ES2017 引入了Object.getOwnPropertyDescriptors()方法,返回指定对象所有自身属性(非继承属性)的描述对象。

const obj = {  foo: 123,  get bar() { return 'abc' }};Object.getOwnPropertyDescriptors(obj)// { foo://    { value: 123,//      writable: true,//      enumerable: true,//      configurable: true },//   bar://    { get: [Function: get bar],//      set: undefined,//      enumerable: true,//      configurable: true } }

ES2018(ES9)新增特性

1 对象的扩展运算符

我们知道 ES6 扩展运算符(...)可以把数组或可遍历对象转为用逗号分隔的参数序列。

ES2018 将扩展运算符引入了对象,可以把对象转为用逗号分隔的键值对序列。

1.1 用于对象克隆

var obj = {  name: 'caocao',  age: 100,  say() {    console.log('My Name is '+this.name);  },  getInfo: function(){}};// 克隆对象 objlet obj2 = {...obj};

注意: 同 Object.assign() 相同,用扩展运算符实现对象克隆,会将源对象所有可枚举属性(可遍历属性)复制到目标对象。

1.2 用于合并对象

var obj1 = {  name: 'caocao',  age: 100,  say() {    console.log('My Name is '+this.name);  },  getInfo: function(){}};var obj2 = {  width:100,   height:200,   name: '孙权'};// 合并 Obj1 和 obj2var obj3 = {...obj1, ...obj2};   // {name: "孙权", age: 100, width: 100, say: ƒ, getInfo: ƒ, …}

注意: 如果对象的属性名有重复的,后面的会覆盖前面的

1.3 用于解构赋值

对象的解构赋值用于从一个对象取值,相当于将目标对象自身的所有可遍历的(enumerable)、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。

let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }

let {...obj5} = [10,20,30,40,40,50];
obj5;  // {0: 10, 1: 20, 2: 30, 3: 40, 4: 40, 5: 50}

let {...obj6} = 'hello world';
obj6; // {0: "h", 1: "e", 2: "l", 3: "l", 4: "o", 5: " ", 6: "w", 7: "o", 8: "r", 9: "l", 10: "d"}

解构赋值必须是最后一个参数,否则会报错。

let { ...x, y, z } = someObject; // 句法错误
let { x, ...y, ...z } = someObject; // 句法错误

ES2019(ES10)新增特性

1. 字符串实例新增方法

对字符串实例新增了 trimStart()trimEnd() 这两个方法。

它们的行为与 trim() 一致,trimStart() 消除字符串头部的空格,trimEnd() 消除尾部的空格。

它们返回的都是新字符串,不会修改原始字符串。

const s = '  abc  ';s.trim() // "abc"s.trimStart() // "abc  "s.trimEnd() // "  abc"

注意: trim() 方法是 ES5 标准中规定的

2 数组实例新增方法

① flat() 方法

flat 方法用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。

flat() 方法的参数是一个整数,表示想要拉平的层数,默认为1,表示拉平一层。如果不管有多少层嵌套,都要转成一维数组,可以用Infinity关键字作为参数。

如果原数组有空位,flat()方法会跳过空位。

[1, 2, [3, 4]].flat();    // [1, 2, 3, 4][1, 2, [3, [4, 5]]].flat();    // [1, 2, 3, [4, 5]][1, 2, [3, [4, 5]]].flat(2);    // [1, 2, 3, 4, 5][1, [2, [3]]].flat(Infinity);    // [1, 2, 3][1, 2, , 4, 5].flat()    // [1, 2, 4, 5]

② flatMap 方法

flatMap()方法对原数组的每个成员执行一个回调函数(相当于执行Array.prototype.map()),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。

flatMap()方法的参数是一个回调函数,该回调函数可以接受三个参数,分别是当前数组成员、当前数组成员的位置(从零开始)、原数组。

flatMap()只能展开一层数组。

[2, 3, 4].flatMap((x) => [x, x * 2])        // [2, 4, 3, 6, 4, 8]; 相当于 [[2, 4], [3, 6], [4, 8]].flat()[['a','b','c'], ['A', 'B', 'C', 'D'], 100].flatMap((item,index) => {  if (item instanceof Array) {    item.push('我是追加的');  }  return item;});     // ["a", "b", "c", "我是追加的", "A", "B", "C", "D", "我是追加的", 100][1, 2, 3, 4].flatMap(x => [[x * 2]]);    // [[2], [4], [6], [8]]  只能展开一层数组。

3 Symbol 实例新增方法

ES2019 提供了一个实例属性description,直接返回 Symbol 的描述。

const sym = Symbol('foo');sym.description // "foo"

ES2020(ES11)新增特性

1. 新增的运算符

1.1 可选链运算符 ?.

可选链运算符( ?. )允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。

?. 操作符的功能类似于 . 链式操作符,不同之处在于,在引用为空(null 或者 undefined) 的情况下不会引起错误,该表达式短路返回值是 undefined。与函数调用一起使用时,如果给定的函数不存在,则返回 undefined

当尝试访问可能不存在的对象属性时,可选链操作符将会使表达式更短、更简明。

const adventurer = {  name: 'Alice',  cat: {    name: 'Dinah'  }};const dogName = adventurer.dog?.name;console.log(dogName);   //  undefinedconsole.log(adventurer.someNonExistentMethod?.()); // undefined

1.2 空值合并运算符 ??

空值合并运算符(**??**)是一个逻辑操作符,当左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数。

与逻辑或运算符符(||)不同,逻辑或操作符会在左侧操作数为假值时返回右侧操作数。也就是说,如果使用 || 来为某些变量设置默认值,可能会遇到意料之外的行为。比如为假值(例如,''0)时。

const foo = null ?? 'default string';console.log(foo);  // "default string"const baz = 0 ?? 42;console.log(baz);  // 0

2. 新增的原始类型 bigint

JavaScript 所有数字都保存成 64 位浮点数,这给数值的表示带来了两大限制。一是数值的精度只能到 53 个二进制位(相当于 16 个十进制位),大于这个范围的整数,JavaScript 是无法精确表示的,这使得 JavaScript 不适合进行科学和金融方面的精确计算。二是大于或等于2的1024次方的数值,JavaScript 无法表示,会返回Infinity

ES2019 引入了一种新的数据类型 BigInt(大整数),来解决这个问题,这是 ECMAScript 的第八种数据类型。BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。

为了与 Number 类型区别,BigInt 类型的数据必须添加后缀n

1234 // 普通整数1234n // BigInt// BigInt 的运算1n + 2n // 3n

BigInt 同样可以使用各种进制表示,都要加上后缀n

0b1101n // 二进制0o777n // 八进制0xFFn // 十六进制

BigInt 与普通整数是两种值,它们之间并不相等。

42n === 42 // false

typeof运算符对于 BigInt 类型的数据返回bigint

typeof 123n // 'bigint'

BigInt 可以使用负号(-),但是不能使用正号(+)。

-42n // 正确
+42n // 报错

总结

1 ECMAScript 中的数据类型

原始类型(值类型): string、number、boolean、null、undefined、symbol、bigint 共七种
对象类型(引用类型): array、object、regexp、set、,map......

2 ECMAScript中声明变量的方式

共六种方式:
1、 var
2、 function
3、 let
4、 const (常量)
5、 class
6、 import

let、const、class、import 都是 ES6 新增的方式,所声明的变量(常量)都具备 4 个特点:

  1. 不能重复声明。
  2. 不能提升。
  3. 不会作为全局对象的属性。
  4. 具有会计作用域。

3 实现数组扁平化的方式

//1. 使用flat() 
arr.flat(Infinity)

//2. 利用字符串的join方法  缺点:数组的元素都会变为字符串类型
arr.join().split(',')

//3. 自定义递归函数
function flatArray(array) {
    //创建一个空数组
    let res = [];
    // 遍历传进来的数组
    for (var i = 0; i < array.length; i ++) {
        //判断数组的元素还是不是数组
        if (array[i] instanceof Array) {
            res = res.concat(flatArray(array[i]));
        } else {
            res.push(array[i]);
        }
    }
    //返回新数组
    return res;
}

4 实现对象拷贝(浅拷贝)

数组 Array:
1. [...arr]
2. arr.concat()  不写参数 数组合并方式
3. arr.splice(0)  利用数组截取方式

对象 Object:
1. {...obj}
2. Object.assign({}, obj)   对象合并方式

5 实现对象的深度克隆(深拷贝)

// 1. 借助于JSON  无法拷贝方法,适合于纯数据对象
JSON.parse(JSON.stringify(obj));

// 2. 递归函数实现
//定义函数 获取对象的构造函数(类)名
function getObjectClass(obj) {
    return Object.prototype.toString.call(obj).slice(8,-1)
}
//深拷贝的函数
function deepClone(obj) {
    //判断obj是对象是数组还是其他
    if (getObjectClass(obj) === 'Object') {
        var res = {};   //创建空的对象
    } else if (getObjectClass(obj) === 'Array') {
        var res = [];   //创建空数组
    } else {
        return obj;
    }
    //对传入的对象(遍历)进行遍历
    for (let i in obj) {
        res[i] = deepClone(obj[i]);
    }
    //返回新数组或对象
    return res;
}

文章作者: coderhyh
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 coderhyh !
评论
评论
  目录