JavaScript进阶(四)

JavaScript进阶(四)

2019版黑马程序员javaScript进阶面向对象ES6 122集教程,哔哩哔哩链接:https://www.bilibili.com/video/BV1Kt411w7MP?from=search&seid=936655930091245798&spm_id_from=333.337.0.0

2020版尚硅谷Web前端ES6教程,涵盖ES6-ES11 68集教程,哔哩哔哩链接:https://www.bilibili.com/video/BV1uK411H7on?spm_id_from=333.999.0.0

ECMAScript 6-11

ES5和ES6的作用域详解

在ES5中,只有全局作用域和函数作用域,无块级作用域。这会导致函数作用域覆盖了全局作用域;亦或者循环中的变量泄露为全局变量。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        /*1.函数作用域覆盖了全局作用域,发生了变量提升,函数声明大于var声明的变量,
            因此函数里面的a提到了前面,
            在打印a,初始化一个undefined给a,所以打印出了undefined。*/
        var a = '1';
        function fn() {
            console.log(a);
            if (3 < 2) {
                var a = 3;
            }
        }
        fn(); // undefined

        // 2.循环中的变量泄露为全局变量
        for (var i = 0; i < 5; i++) {
            console.log(i); //依次输出为0、1、2、3、4
        }
        console.log(i); // 输出为5;
	</script>
</body>
</html>

image-20200717212154654

ES6的块级作用域

用let命令新增了块级作用域,外层作用域无法获取到内层作用域,非常安全明了。即使外层和内层都使用相同变量名,也都互不干扰。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        // es6中的块级作用域
        // 1.外层作用域无法获取到内层作用域
        function fn1() {
            let a = 41;
            if (1 == 1) {
                let a = 3;
                console.log(2, a); // 2 3
            }
            console.log(1, a); // 1 41
        }
        fn1();

        // 2. 外层和内层都使用相同变量名,也都互不干扰
        {
            {
                let food = 'apple';
                console.log(food); // apple
            }
            let food = 'orange';
            console.log(food); // orange
        }

    </script>
</body>

</html>

块级作用域和和函数声明

在ES5中,函数只能在顶级作用域和函数作用域中声明,不能在块级作用域中声明。但是在ES6中,函数可以在块级作用域中声明。

但是会有一定的问题,因为函数声明会被提到代码的最前面。所以会报错,最好在ES6中用函数表达式来表示一个函数。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        //1.函数声明报错
        // {
        //     if (4 < 2) {
        //         function fn() {
        //             console.log('我在函数里面!');
        //         }
        //     }
        // }
        // fn(); // Uncaught TypeError: fn is not a function
        
        //2.函数表达式没错
        {
            let fa = '111';
            let fn = function () {
                console.log('我在函数里面!');
            }
            console.log(fa, fn); // 111 ƒ () { console.log('我在函数里面!');}
        }
        
        // 注意:ES6中允许函数在块级作用域中可以声明的条件是必须在大括号里面,否则就会报错?。
        // 1.报错的情况
        // if (4 > 2) {
        //     let fn2 = function () {
        //         console.log("nih");
        //     };
        // }
        // console.log(fn2);
    </script>
</body>

</html>

eval全局作用域

问题描述:
在使用eval()执行代码的时候,遇到作用域的问题。eval()语句写于函数内,因此eval()内执行的变量也只是局部变量。函数外部无法访问。

问题分析:

var x = 10;
function testEval() {
	eval("var x = 20");//局部变量
}
testEval();
console.log(x); //10

使用window.eval(),谷歌环境测试:

var x = 10;
function testEval() {
	window.eval("var x = 20");//全局变量
}
testEval();
console.log(x); //20

注意:
window.eval()需要考虑浏览器兼容性:

​ IE6/7/8中,eval和window.eval一样,都是局部作用域

​ Firefox/Safari/Chrome/Opera/IE9中,window.eval即使写在自定义函数内使用的也是全局作用域。

使用全局eval()

将eval赋值给变量,使其作为一个全局函数调用:

var x = 10;
var geval = eval;
function testEval() {
	geval("var x = 20");//全局变量
}
testEval();
console.log(x); //20

解决方法:
在自定义函数内要使eval内的变量具有全局作用域,可以使用以下两种方法:

  1. 使用window.eval(),需要考虑浏览器兼容性
  2. eval赋值给变量,使之具有全局作用域

js中的全局变量与局部变量

1.全局变量:声明在函数外部的变量(所有没有var直接赋值的变量都属于全局变量)

2.局部变量:声明在函数内部的变量(所有没有var直接赋值的变量都属于全局变量)

JS中变量申明分显式申明和隐式申明。

​ var i=100;//显式申明

​ i=100;//隐式申明

在函数中使用var关键字进行显式申明的变量是做为局部变量,而没有用var关键字,使用直接赋值方式声明的是全局变量。

当我们使用访问一个没有声明的变量时,JS会报错。而当我们给一个没有声明的变量赋值时,JS不会报错,相反它会认为我们是要隐式申明一个全局变量,这一点一定要注意。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        //输出undefind,全局变量在整个上下文都有效只是在没有赋值之前调用,会输出undefind
        alert(c);

        //alert(d);报错,Uncaught ReferenceError: d is not defined
        var c = 3;
        function test() {
            var a = 1;
            b = 2;
            alert(c)//下面调用后输出3  
        }

        alert(c);//输出3  
        
        test();//输出3  
    </script>
</body>

</html>

函数作用域是针对局部变量来说的,在函数中定义的变量在函数外不能获取

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        function test() {
            alert(a);//声明未赋值输出undefine  
            var a = 1;
            alert(a);//1  
        }
        test();

        //alert(a); //报错,外部获取不到  
    </script>
</body>

</html>

对回调函数的理解

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。

在JavaScript中,回调函数具体的定义为:函数A作为参数(函数引用)传递到另一个函数B中,并且这个函数B执行函数A。我们就说函数A叫做回调函数。如果没有名称(函数表达式),就叫做匿名回调函数。

实例1

  //定义主函数,回调函数作为参数
function A(callback) {
    callback();  
    console.log('我是主函数');      
}

//定义回调函数
function B(){
    setTimeout("console.log('我是回调函数')", 3000);//模仿耗时操作  
}

//调用主函数,将函数B传进去
A(B);
//输出结果
我是主函数
我是回调函数

实例2

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        function a() {
            console.log("执行a函数");
            setTimeout(function () {
                console.log("执行a函数的延迟函数");
            }, 1000);
        }
        function b() {
            console.log("执行函数b");
        }

        a();
        b();
    </script>
</body>
</html>

image-20200720162025175

以上代码会先执行函数a,而且不会等到a中的延迟函数执行完才执行函数b, 在延迟函数被触发的过程中就执行了函数b,当js引擎的event 队列空闲时才会去执行队列里等待的setTimeout的回调函数,这就是一个异步的例子

调用 setTimeout 函数会在一个时间段过去后在队列中添加一个消息。这个时间段作为函数的第二个参数被传入。如果队列中没有其它消息,消息会被马上处理。但是,如果有其它消息,setTimeout 消息必须等待其它消息处理完。因此第二个参数仅仅表示最少的时间 而非确切的时间

所以即使,时间设置为0,也是会照样先执行函数b

回调函数广泛运用在同步(sync)和异步(async)

一、 ECMASript 相关介绍

1.1.什么是 ECMA

ECMA(European Computer Manufacturers Association)中文名称为欧洲计算机制 造商协会,这个组织的目标是评估、开发和认可电信和计算机标准。1994 年后该 组织改名为 Ecma 国际。

1.2.什么是 ECMAScript

ECMAScript 是由 Ecma 国际通过 ECMA-262 标准化的脚本程序设计语言。

1.3.什么是 ECMA-262

Ecma 国际制定了许多标准,而 ECMA-262 只是其中的一个,所有标准列表查看 http://www.ecma-international.org/publications/standards/Standard.htm

1.4.ECMA-262 历史

ECMA-262(ECMAScript)历史版本查看网址 http://www.ecma-international.org/publications/standards/Ecma-262-arch.htm

image-20200717172930664

image-20200717173018256

1.5.谁在维护 ECMA-262

TC39(Technical Committee 39)是推进 ECMAScript 发展的委员会。其会员都是 公司(其中主要是浏览器厂商,有苹果、谷歌、微软、因特尔等)。TC39 定期 召开会议,会议由会员公司的代表与特邀专家出席

1.6.为什么要学习 ES6

ES6 的版本变动内容最多,具有里程碑意义

ES6 加入许多新的语法特性,编程实现更简单、高效

ES6 是前端发展趋势,就业必备技能

1.7.ES6 兼容性

http://kangax.github.io/compat-table/es6/ 可查看兼容性

二、 ECMASript 6 新特性

2.1.let 关键字

let 关键字用来声明变量,使用 let 声明的变量有几个特点:

​ 1) 不允许重复声明

​ 2) 块级作用域

​ 3) 不存在变量提升

​ 4) 不影响作用域链

应用场景:以后声明变量使用 let 就对了

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>let</title>
</head>
<body>
    <script>
        //声明变量
        let a;
        let b,c,d;
        let e = 100;
        let f = 521, g = 'iloveyou', h = [];

        //1. 变量不能重复声明,但是如果使用var的话可以,不会报错
        // let star = '罗志祥';
        // let star = '小猪';  

        //2. 块级作用域     es5:全局作用域, 函数作用域, eval作用域(严格模式中才出现)
        // 像if else while for 这些循环里也是块级作用域
        // {
        		//如果用var声明的话就可以读取到,因为var在代码里没有块级作用域,
       			//会往全局的window里面添加这个属性,所以可以读取到
        //     let girl = '周扬青'; 
        // }
        // console.log(girl);

        //3. 不存在变量提升
        // console.log(song);  //会报错,用let声明的不允许在变量声明之前使用
        // let song = '恋爱达人';
			//输出为undefined,它在执行之前会收集这个变量
        	//(注意要有这个变量,前面和后面声明都可以,不然会报错,说未定义),
        	//收集成功后相当于在他前面添加了一个 var song;
        // console.log(song);  
        // var song = '恋爱达人';


        //4. 不影响作用域链
        {
            let school = '尚硅谷';
            function fn(){
                //函数作用域里没有school这个变量,会向上一级作用域里去找这个变量
                console.log(school);
            }
            fn(); //输出为 尚硅谷,
        }

    </script>
</body>
</html>

let实践案例

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>点击 DIV 换色</title>
    <link crossorigin="anonymous" href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.min.css"
        rel="stylesheet">
    <style>
        .item {
            width: 100px;
            height: 50px;
            border: solid 1px rgb(42, 156, 156);
            float: left;
            margin-right: 10px;
        }
    </style>
</head>

<body>
    <div class="container">
        <h2 class="page-header">点击切换颜色</h2>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
    </div>
    <script>
        //获取div元素对象
        let items = document.getElementsByClassName('item');

        //遍历并绑定事件
        //注意这里不能用var声明,因为执行完这个循环,var已经变为3
        for(let i = 0;i<items.length;i++){ 
            items[i].onclick = function(){
                //修改当前元素的背景颜色
                // this.style.background = 'pink';
                items[i].style.background = 'pink';
            }
        }      
    </script>
</body>

</html>

image-20200718094108478

2.2. const 关键字

const 关键字用来声明常量,const 声明有以下特点 :

​ 1) 声明必须赋初始值

​ 2) 标识符一般为大写

​ 3) 不允许重复声明

​ 4) 值不允许修改

​ 5) 块级作用域

注意: 对象属性修改和数组元素变化不会出发 const 错误

应用场景:声明对象类型使用 const,非对象类型声明选择 let

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>const 定义常量</title>
</head>
<body>
    <script>
        //声明常量
        const SCHOOL = '尚硅谷';

        //1. 一定要赋初始值
        // const A;  //报错

        //2. 一般常量使用大写(潜规则)
        // const a = 100;  //不会报错
        
        //3. 常量的值不能修改
        // SCHOOL = 'ATGUIGU';  //报错
        
        //4. 块级作用域
        // {
        //     const PLAYER = 'UZI';
        // }
        // console.log(PLAYER); //报错
        
        //5. 对于数组和对象的元素修改, 不算做对常量的修改, 不会报错
        const TEAM = ['UZI','MXLG','Ming','Letme'];
        TEAM.push('Meiko');
        // TEAM = 100; //报错
        console.log(TEAM);  //["UZI", "MXLG", "Ming", "Letme", "Meiko"]
        
    </script>
</body>
</html>

2.3.变量的解构赋值

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

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

//对象的解构赋值
const lin = {
 name: '林志颖',
 tags: ['车手', '歌手', '小旋风', '演员']
};
let {name, tags} = lin;

//复杂解构
let wangfei = {
 name: '王菲',
 age: 18,
 songs: ['红豆', '流年', '暧昧', '传奇'],
 history: [
 {name: '窦唯'},
 {name: '李亚鹏'},
 {name: '谢霆锋'}
 ]
};
//用songs去匹配wangfei中的songs属性,并将匹配好的依次赋值
let {songs: [one, two, three, four], history: [first, second, third]} = wangfei;

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

代码演示:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>变量的解构赋值</title>
</head>

<body>
    <script>
        //ES6 允许按照一定模式从数组和对象中提取值,对变量进行赋值,
        //这被称为解构赋值。
        //1. 数组的结构
        // const F4 = ['小沈阳','刘能','赵四','宋小宝'];
        // let [xiao, liu, zhao, song] = F4;
        // console.log(xiao);
        // console.log(liu);
        // console.log(zhao);
        // console.log(song);

        //2. 对象的解构
        const zhao = {
            name: '赵本山',
            age: '不详',
            xiaopin: function(){
                console.log("我可以演小品");
            }
        };

        // let {name, age, xiaopin} = zhao;
        // console.log(name);
        // console.log(age);
        // console.log(xiaopin);
        // xiaopin();

        let { xiaopin, a } = zhao;
        xiaopin();//我可以演小品
        console.log(a); //输出为undefined,注意如果是对象的解构,名字要匹配(相同)

        
        let wangfei = {
            name: '王菲',
            age: 18,
            songs: ['红豆', '流年', '暧昧', '传奇'],
            history: [
                { name: '窦唯' },
                { name: '李亚鹏' },
                { name: '谢霆锋' }
            ]
        };
        /*用songs去匹配wangfei中的songs属性,并将匹配好的依次赋值
        */
        let { songs: [one, two, three, four], history: [first, second, third] } = wangfei; 
        //红豆 流年 暧昧 传奇 {name: "窦唯"} {name: "李亚鹏"} {name: "谢霆锋"}
        console.log(one,two,three,four,first,second,third); 
    </script>
</body>

</html>

2.4.模板字符串

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

​ 1) 字符串中可以出现换行符

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

// 定义字符串
let str = `<ul>
 			<li>沈腾</li>
			 <li>玛丽</li>
			 <li>魏翔</li>
			 <li>艾伦</li>
		   </ul>`;
// 变量拼接
let star = '王宁';
let result = `${star}在前几年离开了开心麻花`;

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

代码演示:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>模板字符串</title>
</head>

<body>
    <script>
        // ES6 引入新的声明字符串的方式 『``』 '' "" 
        //1. 声明
        let str = `我也是一个字符串哦!`;
        console.log(str, typeof str); //我也是一个字符串哦! string

        /*2. 内容中可以直接出现换行符,我们就可以不用再拼接了
        let str3 = '<tr class="tabPeo"><td>'
                    + data[i].name
                    + '</td><td>'
                    + data[i].address
                    + '</td><td>'
                    + data[i].state
                    + '</td></tr>'
       */
        let str2 = `<ul>
                    <li>沈腾</li>
                    <li>玛丽</li>
                    <li>魏翔</li>
                    <li>艾伦</li>
                    </ul>`;
        
        //3. 变量拼接
        let lovest = '魏翔';
        let out = `${lovest}是我心目中最搞笑的演员!!`;
        console.log(out); //魏翔是我心目中最搞笑的演员!!

    </script>
</body>

</html>

2.5.简化对象写法

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

let name = '尚硅谷';
let slogon = '永远追求行业更高标准';
let improve = function () {
 console.log('可以提高你的技能');
}
//属性和方法简写
let atguigu = {
 name,
 slogon,
 improve,
 change() {
 console.log('可以改变你')
 }
};

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

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>简化对象写法</title>
</head>
<body>
    <script>
        //ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。
        //这样的书写更加简洁
        let name = '尚硅谷';
        let change = function(){
            console.log('我们可以改变你!!');
        }

        const school = {
            name, //es6里允许我们只把变量放进来
            change,
            improve(){
                console.log("我们可以提高你的技能");
            }
        }
        /*上面的写法相当于:
            const school = {
            name: name,
            change: change,
            improve: function(){
                console.log("我们可以提高你的技能");
            }
        }
        */
        console.log(school); //{name: "尚硅谷", change: ƒ, improve: ƒ}

    </script>
</body>
</html>

2.6.箭头函数

ES6 允许使用「箭头」(=>)定义函数。

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

箭头函数的注意点:
	1) 如果形参只有一个,则小括号可以省略
	2) 函数体如果只有一条语句,则花括号可以省略,函数的返回值为该条语句的执行结果
	3) 箭头函数 this 指向声明时所在作用域下 this 的值
	4) 箭头函数不能作为构造函数实例化
	5) 不能使用 arguments

/**
* 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();
 }
};

注意:箭头函数不会更改 this 指向,用来指定回调函数会非常合适

代码演示:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>箭头函数</title>
</head>

<body>
    <script>
        // ES6 允许使用「箭头」(=>)定义函数。
        //声明一个函数
        let fn = (a,b) => {
            return a + b;
        }
        //调用函数
        let result = fn(1, 2);
        console.log(result); //3


        //1. this 是静态的. this 始终指向函数声明时所在作用域下的 this 的值
        function getName(){
            console.log(this.name);
        }
        let getName2 = () => {
            console.log(this.name);
        }

        //设置 window 对象的 name 属性
        window.name = '尚硅谷';
        const school = {
            name: "ATGUIGU"
        }

        //直接调用,this都是指向全局对象window
        getName();//尚硅谷
        getName2();//尚硅谷

        //call 方法调用,用call方法改变this指向
        getName.call(school);//ATGUIGU
        //尚硅谷,因为箭头函数中的this 始终指向函数声明时所在作用域下的 this 的值
        getName2.call(school);


        //2. 不能作为构造函数,实例化对象,以下写法会报错,说Person不是一个构造器
        // let Person = (name, age) => {
        //     this.name = name;
        //     this.age = age;
        // }
        // let me = new Person('xiao',30);
        // console.log(me);


        //3. 不能使用 arguments 变量,报错,说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;

            /*相当于
                let pow = (n) =>{
                    return n * n;
                }
            */     
            console.log(pow(8));//64
    </script>
</body>

</html>

箭头函数的实践与应用场景

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>箭头函数实践</title>
    <style>
        div {
            width: 200px;
            height: 200px;
            background: #58a;
        }
    </style>
</head>
<body>
    <div id="ad"></div>
    <script>
        //需求-1  点击 div 2s 后颜色变成『粉色』
        //获取元素
        /*
        let ad = document.getElementById('ad');
        //绑定事件
        ad.addEventListener("click", function(){
            //保存 this 的值
            let _this = this;
            //定时器
            setTimeout(function() {
                //修改背景颜色 this
                //这里的this指向的是window,而window没有style这个属性,为undefined,
                //所以下面用this.style.background = 'pink'就会报错
                console.log(this);
                // this.style.background = 'pink';
                //当函数调用时,在当前的作用域下找不到_this,就会往外层的作用域下面去找
                _this.style.background = 'pink'; 
            }, 2000);
        });
        */

        //箭头函数的写法 
        let ad2 = document.getElementById('ad');
        // //绑定事件
        ad2.addEventListener("click", function(){
            //保存 this 的值
            // let _this = this;
            //定时器
            setTimeout(() => {
                //修改背景颜色 this
                // console.log(this);
                // _this.style.background = 'pink';
                //箭头函数中this指向的是在声明时的作用域下的this值,
                //他是在function的作用域下声明的,所以他指向的是function函数的this值,
                //而这个函数的this又指向的是事件源ad
                this.style.background = 'pink';
            }, 2000);
        });


        //需求-2  从数组中返回偶数的元素
        const arr = [1,6,9,10,100,25];
        // 不用箭头函数
        // const result = arr.filter(function(item){
        //     if(item % 2 === 0){
        //         return true;
        //     }else{
        //         return false;
        //     }
        // });
        
        const result = arr.filter(item => item % 2 === 0);
        // 相当于:
        // const result = arr.filter( item => {
        //     if(item %2 === 0){
        //         return true;
        //     }else{
        //         return false;
        //     }
        // })
        console.log(result);//[6,10,100]

        // 箭头函数适合与 this 无关的回调. 定时器, 数组的方法回调
        // 箭头函数不适合与 this 有关的回调.  事件回调, 对象的方法

        /*
     array.filter(function(currentValue,index,arr), thisValue)

     function(currentValue, index,arr)	必须。函数,数组中的每个元素都会执行这个函数
                                                
                             函数参数:
                             参数	        描述
                             currentValue	必须。当前元素的值
                             index	        可选。当前元素的索引值
                             arr	        可选。当前元素属于的数组对象
            
            thisValue	可选。对象作为该执行回调时使用,传递给函数,
                        用作 "this" 的值。如果省略了 thisValue ,"this" 的值为 "undefined"

            返回值:	返回数组,包含了符合条件的所有元素。如果没有符合条件的元素则返回空数组。
        */

    </script>
</body>
</html>

函数参数的默认值设置

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>函数参数默认值</title>
</head>
<body>
    <script>
        //ES6 允许给函数参数赋值初始值
        //1. 形参初始值 具有默认值的参数, 一般位置要靠后(潜规则),
        function add(a,b,c=10) {
            return a + b + c;
        }
        //输出为13,我们没有给c传值,则默认使用初始值,如果我们改为let result = add(1,2,3);则输出为6
        let result = add(1,2);
        console.log(result);

        function add2(a,b,c) {
            return a + b + c;
        }
        let result2 = add2(1,2);//输出为NaN,因为没有给c传值,则c为undefined,1+2+undefined=NaN
        console.log(result2);

        function add3(a,c=10,b) {
            return a + b + c;
        }
        let result3 = add3(1,2);//输出为NaN, 因为我们传值a=1,c=2,b没有传值所以输出为undefined
        console.log(result3);


        //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: 'atguigu.com',//如果我们此处不写这行,则输出为默认给的值127.0.0.1
            username: 'root',
            password: 'root',
            port: 3306
        })
        
        //也可以这样写
        function connect2(options){
            let host = options.host;
            let username = options.username;
            let password = options.password;
            let port = options.port;
            console.log(host);
            console.log(username);
            console.log(password);
            console.log(port);
        }
        connect2({
            host: 'atguigu.com',//如果我们此处不写这行,则输出为默认给的值127.0.0.1
            username: 'root',
            password: 'root',
            port: 3306
        })
        
    </script>
</body>
</html>

类数组对象:arguments

当我们在js中在调用一个函数的时候,我们经常会给这个函数传递一些参数,js把传入到这个函数的全部参数存储在一个叫做arguments的东西里面

在Javascript中arguments是一个类似Array的存在,但它不能等同于Array。

arguments对象不能显式的创建,它只有在函数开始时才可用。

它可以像访问数组那样去访问每一个子元素。PS:arguments[0],arguments[1],。。。。。。。。arguments[n];

Javascript并没有重载函数的功能,但是通过Arguments对象能够模拟重载。

Javascript的每个函数都会有一个Arguments对象实例arguments,它引用着函数的实参,它也有类似于数组的length属性。

​ (1)、arguments.length 为函数实参个数。

​ (2)、arguments.callee 引用函数自身。

1、我们可以借用arguments.length可以来查看实参和形参的个数是否一致:

function add(a, b) {
  var realLen = arguments.length;
  console.log("realLen:", arguments.length);
  var len = add.length;
  console.log("len:", add.length);
  if (realLen == len) {
  	console.log('实参和形参个数一致');
  } else {
  	console.log('实参和形参个数不一致');
  }
};
add(1,2,3,6,8);

2、我们可以借用arguments.callee来让匿名函数实现递归:

var sum = function(n) {
	if(n == 1) {
		return 1;
  } else {
  	return n + arguments.callee(n-1);
 }
}
console.log("sum =", sum(5));

3.arguments是什么

function showargs() {
	console.log( arguments );
}

showargs(1,2,3,4,5);

image-20200718215357337

	这里我们可以看到arguments对象将我传入的五个参数以数组的形式保存在里面,
还有保存了我传入函数的实参的个数(length)。而且我们可以看到arguments对象
的 __ proto __ 是指向object的,这也说明了他是个类数组对象,而不是一个数组。

	有了这个对象我们以后写函数的时候,就不用给所有的形参指定参数名,然后通过
参数名的方式获取参数了,我们可以直接使用arguments对象来获取实参,

	有些语言在我们给函数指定了参数名之后,当调用函数时,会判断当前传入的参数是否与函数
定义的参数个数相等,不相等就会报错,但是灵活的js(不是我说,js是真的灵活)并不会验证
传递给函数的参数个数是否等于函数定义的参数个数。所以为了代码的简洁度,我们使用arguments
调用参数可以不混淆不同函数之间的参数名。另外为了代码的严整度,我们也能用arguments来
判断当前传入参数的个数是否与我们需要的数量一致。

下面举个栗子:
function add() {
	if( arguments.length == 2 ){
		return arguments[0] + arguments[1];
	}else{
		return '传入参数不合法';
	}
}

console.log( add(2,3) );
console.log( add(1,2,3) );

image-20200718215623478

	最后我们还可以看到arguments还有一个叫做callee的属性,这个属性是表示
的是当前函数的一个引用,简单点说,这个属性里面存储的我们调用的这个函数的代码,
实在无法理解的时候,又到了console.log大显身手的时候了。
function showcallee() {
    var a = '这里是代码';
    var b = '这是另一段代码';
    var c = a + b;
    
    console.log(arguments.callee);
    
    return c;
}
showcallee();

image-20200718215722296

看到结果的你是不是和我一样惊呆了呢,这不就是我写的代码吗,arguments.callee完完整整的把这个函数的这段代码返回了。

arguments的一些妙用

1.利用arguments实现方法的重载

下面我们利用arguments对象来实现一个参数相加的函数,不论传入多少参数都行,将传入的参数相加后返回。

function add() {
    var len = arguments.length,
        sum = 0;
    for(;len--;){
        sum += arguments[len];
    }
    return sum;
}

console.log( add(1,2,3) );   //6
console.log( add(1,3) );     //4
console.log( add(1,2,3,5,6,2,7) );   //26

由于js是一种弱类型的语言,没有重载机制,当我们重写函数时,会将原来的函数直接覆盖,这里我们能利用arguments,来判断传入的实参类型与数量进行不同的操作,然后返回不同的数值。

2.利用arguments.callee实现递归

先来看看之前我们是怎么实现递归的,这是一个结算阶乘的函数

function factorial(num) { 
    if(num<=1) { 
    	return 1; 
    }else { 
    	return num * factorial(num-1); 
    } 
} 

但是当这个函数变成了一个匿名函数时,我们就可以利用callee来递归这个函数。

function factorial(num) { 
    if(num<=1) { 
    	return 1; 
    }else { 
    	return num * arguments.callee(num-1); 
    } 
} 

这个方法虽然好用,但是有一点值得注意,ECMAScript4中为了限制js的灵活度,让js变得严格,新增了严格模式,在严格模式中我们被禁止不使用var来直接声明一个全局变量,当然这不是重点,重点是arguments.callee这个属性也被禁止了。不过这都不是事儿,ES6为我们新增了很多好用的变量声明方式和新的语法糖,作为一个时髦的前端,我们赶紧学习一些ES6的新语法吧。

2.7. rest 参数

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

/**
* 作用与 arguments 类似
*/
function add(...args){
 	console.log(args);
}
add(1,2,3,4,5);

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

注意:rest 参数非常适合不定个数参数函数的场景

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>rest参数</title>
</head>
<body>
    <script>
        // ES6 引入 rest 参数,用于获取函数的实参,用来代替 arguments
        // ES5 获取实参的方式
        function date(){
            console.log(arguments);
        }
        date('白芷','阿娇','思慧');//它并不是一个数组,而是一个对象

        // rest 参数,是一个数组,所以我们可以使用数组的方法filter some every map 
        function date2(...args){
            console.log(args);
        }
        date2('阿娇','柏芝','思慧');//它是一个数组

        // rest 参数必须要放到参数最后,不能放前面或中间,会报错
        function fn(a,b,...args){
            console.log(a);
            console.log(b);
            console.log(args);
        }
        fn(1,2,3,4,5,6);

    </script>
</body>
</html>

image-20200719101657352

2.8. spread 扩展运算符

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

/**
* 展开数组
*/
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};

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>扩展运算符</title>
</head>
<body>
    <script>
        // 『...』 扩展运算符能将『数组』转换为逗号分隔的『参数序列』
        //声明一个数组 ...
        const tfboys = ['易烊千玺','王源','王俊凯'];
        // => '易烊千玺','王源','王俊凯'

        // 声明一个函数
        function chunwan(){
            console.log(arguments);
        }
        chunwan(tfboys);
        // 下面的这行代码相当于 chunwan('易烊千玺','王源','王俊凯')
        //注意和rest的区分,rest是放在函数声明的形参的位置,而扩展运算符...是放在调用函数的实参里
        chunwan(...tfboys);
    </script>
</body>
</html>

image-20200719232650640

扩展运算符的应用
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>扩展运算符应用</title>
</head>
<body>
    <div></div>
    <div></div>
    <div></div>
    <script>
        //1. 数组的合并 
        const kuaizi = ['王太利','肖央'];
        const fenghuang = ['曾毅','玲花'];
        // es5的做法
        /*
            concat() 方法用于连接两个或多个数组。该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。
            
            语法  arrayObject.concat(arrayX,arrayX,......,arrayX)
                 参数       描述
                 arrayX	  必需。该参数可以是具体的值,也可以是数组对象。可以是任意多个。
            
            返回值 
                返回一个新的数组。该数组是通过把所有 arrayX 参数添加
                到 arrayObject 中生成的。如果要进行 concat() 操作的参数是数组,
                那么添加的是数组中的元素,而不是数组。
        */
        // const zuixuanxiaopingguo = kuaizi.concat(fenghuang);
        
        //将一个数组转为用逗号分隔的参数序列,这里相当于将两个数组的值拿
        //出来然后['王太利','肖央','曾毅','玲花']
        const zuixuanxiaopingguo = [...kuaizi, ...fenghuang];
        console.log(zuixuanxiaopingguo);

        //2. 数组的克隆
        const sanzhihua = ['E','G','M'];
        const sanyecao = [...sanzhihua];//注意如果是引用数据类型的话是浅拷贝
        console.log(sanyecao);

        //3. 将伪数组转为真正的数组
        const divs = document.querySelectorAll('div');
        const divArr = [...divs];
        console.log(divArr);                
    </script>
</body>
</html>

image-20200719234256758

2.9.Symbol

2.9.1.Symbol 基本使用

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

Symbol 特点:

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

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

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

//创建 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

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>symbol</title>
</head>
<body>
    <script>
        //创建Symbol
        let s = Symbol();
        console.log(s, typeof s);

        let s2 = Symbol('尚硅谷');//里面的是一个描述字符串,方便我们更好的理解他的作用,就跟注释一样
        let s3 = Symbol('尚硅谷');//这里的Symbol是一个函数
        console.log(s2 ===s3);//false,里面的字符串只是一个标志,他们所返回的结果是不一样的

        //Symbol.for 创建
        let s4 = Symbol.for('尚硅谷');//此时Symbol是一个对象
        //通过这种方式创建的话我们是可以描述字符串得出唯一的Symbol值的
        let s5 = Symbol.for('尚硅谷');
        console.log(s5, typeof s5);
        console.log(s4 ===s5);//true

        //不能与其他数据进行运算
        //全部报错为:Uncaught TypeError: Cannot convert a Symbol value to a number
        //    let result = s + 100; 
        //    let result = s > 100;
        //    let result = s + s;

        // js的七种数据类型:
        //   undefined
        //   string  symbol
        //   object
        //   null number
        //   boolean

    </script>
</body>
</html> 

image-20200719235907481

对象添加Symbol类型的属性
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Symbol 创建对象属性</title>
</head>
<body>
    <script>
        //向对象中添加 up down 方法
        let game = {
            name:'俄罗斯方块',
            up: function(){},
            down: function(){}
        };
        
        //声明一个对象,这个对象里有两个属性,对应的都是Symbol类型的值
        let methods = {
            up: Symbol(),
            down: Symbol()
        };
        // 我们可以向game中添加方法
        game[methods.up] = function(){
            console.log("我可以改变形状");
        }

        game[methods.down] = function(){
            console.log("我可以快速下降!!");
        }
        console.log(game);


        //添加Symbol类型的属性
        let youxi = {
            name:"狼人杀",
            // 直接 Symbol() 是不可以的,因为这是表达式,是一个动态的值,
            //不是一个固定的属性,所以我们应该直接 [Symbol()] 这样的方式来添加属性,
            //当然我们也可以给他添加一个描述字符串
            [Symbol('say')]: function(){
                console.log("我可以发言")
            },
            [Symbol('zibao')]: function(){
                console.log('我可以自爆');
            }
        }
        console.log(youxi)       
    </script>
</body>
</html>

image-20200720110709728

2.9.2.Symbol 内置值

除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。可以称这些方法为魔术方法,因为它们会在特定的场 景下自动执行。

这些都是Symbol的属性;

Symbol.hasInstance 	当其他对象使用 instanceof 运算符,判断是否为该对象的实例时,会调用这个方法

Symbol.isConcatSpreadable 	对象的 Symbol.isConcatSpreadable 属性等于的是一个布尔值,
                            表示该对象用于Array.prototype.concat()时,是否可以展开。
                                    
Symbol.species 	创建衍生对象时,会使用该属性
Symbol.match 	当执行 str.match(myObject) 时,如果该属性存在,会调用它,返回该方法的返回值。

Symbol.replace 	当该对象被 str.replace(myObject)方法调用时,会返回该方法的返回值。

Symbol.search 	当该对象被 str.search (myObject)方法调用时,会返回该方法的返回值。

Symbol.split	当该对象被 str.split(myObject)方法调用时,会返回该方法的返回值。

Symbol.iterator 对象进行 for...of 循环时,会调用 Symbol.iterator 方法,返回该对象的默认遍历器

Symbol.toPrimitive 	该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。

Symbol. toStringTag 在该对象上面调用 toString 方法时,返回该方法的返回值

Symbol. unscopables 该对象指定了使用 with 关键字时,哪些属性会被 with环境排除。

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Symbol内置属性</title>
</head>
<body>
    <script>
        class Person{
            // 跟普通的方法不一样,他有自己的自动执行场景,而且还可以把参数传进来,
            static [Symbol.hasInstance](param){
                //把instanceof前面的值传递给这个方法,并由他来决定何时执行
                console.log(param);
                console.log("我被用来检测类型了");
                return false;
                // return true;
            }
        }
        let o = {};
        console.log(o instanceof Person);


        const arr = [1,2,3];
        const arr2 = [4,5,6];
        console.log(arr.concat(arr2));
        //表示arr2不展开,作为一个整体
        arr2[Symbol.isConcatSpreadable] = false;  
        console.log(arr.concat(arr2));
    </script>
</body>
</html>

image-20200720111954606

2.10. 迭代器

遍历器(Iterator)就是一种机制。它是一种接口,为各种不同的数据结构提 供统一的访问机制。任何数据结构只要部署 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

  1. 工作原理

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

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

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

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

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>迭代器</title>
</head>
<body>
    <script>
        //声明一个数组
        const xiyou = ['唐僧','孙悟空','猪八戒','沙僧'];
        console.log(xiyou);

        //使用 for...of 遍历数组,保存的是键值
        for(let v of xiyou){
            console.log(v);
        }

        // 使用 for...in 遍历数组,保存的是键名
        for(let vv in xiyou){
            console.log(vv);
        }

        //原理实现
        //Symbol.iterator为xiyou数组原型的方法,
        //xiyou[Symbol.iterator]对应的是一个函数,加个()得到
        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());
        console.log(iterator.next());
    </script>
</body>
</html>

image-20200720113829083

迭代器应用:自定义遍历数据

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>自定义遍历数据</title>
</head>

<body>
    <script>
        //声明一个对象
        const banji = {
            name: "终极一班",
            stus: [
                'xiaoming',
                'xiaoning',
                'xiaotian',
                'knight'
            ],
            // 添加接口
            [Symbol.iterator]() {
                //索引变量
                let index = 0;
                let _this = this;
                return {
                    next: function () {
                        if (index < _this.stus.length) {
                            const result = { value: _this.stus[index], done: false };
                            //下标自增
                            index++;
                            //返回结果
                            return result;
                        }else{
                            //表示遍历结束
                            return {value: undefined, done: true};
                        }
                    }
                };
            }
        }
        //遍历这个对象 
        for (let v of banji) {
            console.log(v);
        }
    </script>
</body>

</html>

image-20200720150720120

2.11. 生成器

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

function * gen(){
 	yield '一只没有耳朵';
 	yield '一只没有尾巴';
 	return '真奇怪';
}
let iterator = gen();

console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

代码说明:
1) * 的位置没有限制
2) 生成器函数返回的结果是迭代器对象,调用迭代器对象的 next 方法可以得到 yield 语句后的值
3) yield 相当于函数的暂停标记,也可以认为是函数的分隔符,每调用一次 next 方法,执行一段代码
4) next 方法可以传递实参,作为 yield 语句的返回值

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>生成器</title>
</head>
<body>
    <script>    
        //生成器其实就是一个特殊的函数
        //异步编程  纯回调函数  node fs  ajax mongodb
        
        function * gen(){
            console.log(111);
            yield '一只没有耳朵';//yield为函数代码的分隔符
            console.log(222);
            yield '一只没有尾部';
            console.log(333);
            yield '真奇怪';
            console.log(444);
        }

        let iterator = gen();
        // iterator.next();//输出为111
        // iterator.next();//输出为222
        // iterator.next();//输出为333
        // iterator.next();//输出为444

        console.log(iterator.next());
        console.log(iterator.next());
        console.log(iterator.next());
        console.log(iterator.next());

        //遍历
        for(let v of gen()){
            console.log(v);
        }
    </script>
</body>
</html>

image-20200720152458789

生成器函数参数
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>生成器函数参数</title>
</head>
<body>
    <script>
        function * gen(arg){
            console.log(arg);//输出为AAA
            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());//第一个next方法传参没有结果
        //next方法可以传入实参
        //传的这个参数将作为上一个yield的返回结果,这里为one
        console.log(iterator.next('BBB'));
        console.log(iterator.next('CCC'));
        console.log(iterator.next('DDD'));      
    </script>
</body>
</html>

image-20200720153715076

生成器函数实例1
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>生成器函数实例</title>
</head>

<body>
    <script>
        // 异步编程  文件操作 网络操作(ajax, request) 数据库操作
        // 1s 后控制台输出 111  2s后输出 222  3s后输出 333 
        // 回调地狱
        // setTimeout(() => {
        //     console.log(111);
        //     setTimeout(() => {
        //         console.log(222);
        //         setTimeout(() => {
        //             console.log(333);
        //         }, 3000);
        //     }, 2000);
        // }, 1000);

        function one(){
            setTimeout(()=>{
                console.log(111);
                iterator.next();//执行完这个后执行下一个
            },1000)
        }

        function two(){
            setTimeout(()=>{
                console.log(222);
                iterator.next();
            },2000)
        }

        function three(){
            setTimeout(()=>{
                console.log(333);
                iterator.next();
            },3000)
        }

        function * gen(){
            yield one();
            yield two();
            yield three();
        }

        //调用生成器函数
        let iterator = gen();
        iterator.next();
    </script>
</body>
</html>
生成器函数实例2
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>生成器函数</title>
</head>
<body>
    <script>
        //模拟获取  用户数据  订单数据  商品数据 
        function getUsers(){
            setTimeout(()=>{
                let data = '用户数据';
                //调用 next 方法, 并且将数据传入
                //这是第二个next方法,他的实参将作为第一个yield语句的返回结果,
                //我们在下方声明一个变量接收,获取用户数据
                iterator.next(data);
            }, 1000);
        }

        function getOrders(){
            setTimeout(()=>{
                let data = '订单数据';
                iterator.next(data);
            }, 1000)
        }

        function getGoods(){
            setTimeout(()=>{
                let data = '商品数据';
                iterator.next(data);
            }, 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();//执行第一个yield,并把执行结果返回   
    </script>
</body>
</html>

2.12. Promise

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

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

2) Promise.prototype.then 方法 

3) Promise.prototype.catch 方法
Promise基本语法
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Promise基本语法</title>
</head>
<body>
    <script>
        //实例化 Promise 对象
        const p = new Promise(function(resolve, reject){
            setTimeout(function(){
                //
                let data = '数据库中的用户数据';
                // 调用resolve函数
                //当调用这个后,promise对象p的状态就会变为成功,
                //有三个状态,分别为初始化、成功、失败
                resolve(data);

                let err = '数据读取失败';
                // 调用reject
                reject(err);
            }, 1000);
        });

        //调用 promise 对象的 then 方法,接收两个参数,
        //第一个函数表示成功,第二个函数表示失败
        p.then(function(value){
            console.log(value);//数据库中的用户数据
        }, function(reason){
            console.error(reason);
        })
    </script>
</body>
</html>
Promise封装读取文件
//1. 引入 fs 模块
const fs = require('fs');

//2. 第一种方法:调用方法读取文件
// fs.readFile('./resources/为学.md', (err, data)=>{
//     //如果失败, 则抛出错误
//     if(err) throw err;
//     //如果没有出错, 则输出内容
//     console.log(data.toString());
// });

//3. 第二种方法:使用 Promise 封装读取文件
const p = new Promise(function(resolve, reject){
    fs.readFile("./resources/为学.md", (err, data)=>{
        //判断如果失败
        if(err) reject(err);
        //如果成功
        resolve(data);
    });
});

p.then(function(value){
    console.log(value);
    //使用toString将值转换为String类型,不使用的话
    console.log(value.toString());
}, function(reason){
    console.log("读取失败!!");
});

image-20200720201458248

Promise封装ajax请求
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>发送 AJAX 请求</title>
</head>

<body>
    <script>
        // 接口地址: https://api.apiopen.top/getJoke

        const p = new Promise((resolve, reject) => {
            //1. 创建对象
            const xhr = new XMLHttpRequest();

            //2. 初始化
            //给 https://api.apiopen.top/getJ 发送 get 类型请求,
            xhr.open("GET", "https://api.apiopen.top/getJ");

            //3. 发送
            xhr.send();

            //4. 绑定事件, 处理响应结果
            xhr.onreadystatechange = function () {
                //判断状态
                if (xhr.readyState === 4) {
                    //判断响应状态码 200-299都是表示成功
                    if (xhr.status >= 200 && xhr.status < 300) {
                        //表示成功
                        //调用上方resolve这个函数来修改promise的状态为成功,
                        //xhr.response表示正常输出
                        resolve(xhr.response);
                    } else {
                        //如果失败
                        //调用上方reject这个函数来修改promise的状态为失败
                        reject(xhr.status);
                    }
                }
            }
        })
        
        //指定回调
        p.then(function(value){
            console.log(value);
        }, function(reason){
            console.error(reason);
        });
    </script>
</body>
</html>

image-20200720202630855

Promise.prototype.then方法
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Promise.prototype.then</title>
</head>

<body>
    <script>
        //创建 promise 对象
        const p = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('用户数据');
                // reject('出错啦');
            }, 1000)
        });

        //调用 then 方法  then方法的返回结果是 Promise 对象, 对象状态由回调函数的执行结果决定
        const result = p.then(value => {
            console.log(value);
            // 不写 return ,函数内部默认返回结果为undefined
        }, reason => {
            console.warn(reason);
        });
        console.log(result);


        const result2 = p.then(value => {
            console.log(value);
            //1. 非 promise 类型的属性
            return 'iloveyou';
        }, reason => {
            console.warn(reason);
        });
        console.log(result2);


        //1. 如果回调函数中返回的结果是 非 promise 类型的属性, 状态为成功, 返回值为对象的成功的值
        const result3 = p.then(value => {
            console.log(value);
            //2. 是 promise 对象
            return new Promise((resolve, reject)=>{
                // resolve('ok');
                reject('error');
            });
        }, reason=>{
            console.warn(reason);
        });
        console.log(result3);

        const result4 = p.then(value => {
            console.log(value);
            //3. 抛出错误
            throw new Error('出错啦!');
            // throw '出错啦!';
        }, reason=>{
            console.warn(reason);
        });
        console.log(result4);

        //链式调用
        // p.then(value=>{

        // },reason =>{

        // }).then(value=>{

        // },reason =>{

        // });
    </script>
</body>

</html>

image-20200720205009655

Promise实践:读取多个文件
//引入 fs 模块
const fs = require("fs");

// 回调地狱的方式,这种我们通常用于用户与订单的层层递进关系
// fs.readFile('./resources/为学.md', (err, data1)=>{
//     fs.readFile('./resources/插秧诗.md', (err, data2)=>{
//         fs.readFile('./resources/观书有感.md', (err, data3)=>{
//             let result = data1 + '\r\n' +data2  +'\r\n'+ data3;  //\r\n表示换行
//             console.log(result);
//         });
//     });
// });

//使用 promise 实现
const p = new Promise((resolve, reject) => {
    fs.readFile("./resources/为学.md", (err, data) => {
        resolve(data);//resolve可以改变promise的状态变为成功
    });
});

p.then(value => {//value是第一个文件内容
    return new Promise((resolve, reject) => {
        fs.readFile("./resources/插秧诗.md", (err, data) => {//data是第二个文件内容
            resolve([value, data]);
        });
    });
}).then(value => {//这里的value变为第一个文件和第二个文件形成的数组
    return new Promise((resolve, reject) => {
        fs.readFile("./resources/观书有感.md", (err, data) => {//data是第三个文件
            //压入
            value.push(data);//将第三个文件加入,此时value为三个文件的结果
            //resolve成功,则new的promise又成功了,然后then方法返回的方法有成功了,
            //它一成功我们再来一个then方法
            resolve(value);
        });
    })
}).then(value => {//再一个then方法
    console.log(value.join('\r\n'));//为一个数组,用join拼接,用\r\n拼接
});

image-20200720211109198

Promise.prototype.catch方法
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>catch方法</title>
</head>
<body>
    <script>
        // catch方法用来指定promise失败的回调

        const p = new Promise((resolve, reject)=>{
            setTimeout(()=>{
                //设置 p 对象的状态为失败, 并设置失败的值
                reject("出错啦!");
            }, 1000)
        });

        // 第一种方法:通过then方法
        // p.then(function(value){}, function(reason){
        //     console.error(reason);
        // });

        // 第二种方法:catch方法,失败的回调
        p.catch(function(reason){
            console.warn(reason);
        });
    </script>
</body>
</html>

2.13. Set(集合)

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

	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());

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>集合</title>
</head>
<body>
    <script>
        //声明一个 set
        let s = new Set();
        let s2 = new Set(['大事儿','小事儿','好事儿','坏事儿','小事儿']);

        console.log(s2,typeof s2);//会自动去重

        //元素个数
        console.log(s2.size);
        //添加新的元素
        s2.add('喜事儿');
        console.log(s2.size);
        //删除元素
        s2.delete('坏事儿');
        console.log(s2.size);
        //检测
        console.log(s2.has('糟心事'));
        //清空
        // s2.clear();
        console.log(s2);

        // 集合实现了 iterator 接口,所以我们可以实现遍历
        for(let v of s2){
            console.log(v);
        }
        
    </script>
</body>
</html>

image-20200720212822516

集合实践
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Set 实践</title>
</head>
<body>
    <script>
        let arr = [1,2,3,4,5,4,3,2,1];
        //1. 数组去重
        let result = new Set(arr);//result为一个集合
        let result2 = [...new Set(arr)];//通过扩展运算符...将集合变为数组
        console.log(result);
        console.log(result2);

        //2. 交集
        let arr2 = [4,5,6,5,6];
        //[...new Set(arr)] 表示变为一个去重之后的数组,然后调用filter方法 迭代(遍历)数组元素
        let result3 = [...new Set(arr)].filter(item => {
            let s2 = new Set(arr2);// 4 5 6
            if(s2.has(item)){//如果s2集合中存在这个元素,即为交集
                return true;
            }else{
                return false;
            }
        });
        // 对上面代码的简化
        // let result3 = [...new Set(arr)].filter(item => new Set(arr2).has(item));
        console.log(result3);

        //3. 并集
        //[...arr, ...arr2]表示将数组合并,然后通过new Set去重,最后通过扩展运算符...将集合变为数组
        let union = [...new Set([...arr, ...arr2])];
        console.log(union);

        //4. 差集,差集是相对来说的,比如arr中为1,2,3,arr2中为3,4,5,
        //则arr和arr2的差集为1,2,反过来arr2和arr的差集为4,5
        let diff = [...new Set(arr)].filter(item => !(new Set(arr2).has(item)));
        console.log(diff);

        let diff2 = [...new Set(arr2)].filter(item => !(new Set(arr).has(item)));
        console.log(diff2);
    </script>
</body>

</html>

image-20200721155716535

2.14. Map

ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合。但是“键”的范围不限于字符串,
各种类型的值(包括对象)都可以当作键。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());

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Map</title>
</head>
<body>
    <script>
        //声明 Map
        let m = new Map();

        //添加元素
        m.set('name','尚硅谷');//键为name,值为尚硅谷
        m.set('change', function(){
            console.log("我们可以改变你!!");
        });
        let key = {
            school : 'ATGUIGU'
        };
        //键是对象,值是数组 ['北京','上海','深圳']
        m.set(key, ['北京','上海','深圳']);

        console.log(m);

        //size表示获取Map中的元素个数
        console.log(m.size);

        //删除
        // m.delete('name');

        //获取
        //get方法,在参数里传入键,获取值
        console.log(m.get('change'));
        console.log(m.get(key));

        //清空
        // m.clear();

        //遍历
        for(let v of m){
            console.log(v);
        }
    </script>
</body>
</html>

image-20200721161111269

2.15. class 类

	ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。
    通过 class 关键字,可以定义类。基本上,ES6class 可以看作只是一个语法糖,
        它的绝大部分功能,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(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();
class语法
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>类声明</title>
</head>
<body>
    <script>
        // es5的写法,通过构造函数实例化对象
        //以手机为例子,
        function Phone(brand, price){//表示对实例对象的属性作初始化
            this.brand = brand;
            this.price = price;
            this.sing = function(){//不推荐这样直接添加方法,我们一般用原型对象添加
                console.log("我会唱歌");
            }
        }

        //添加方法,我们通过原型对象去添加,这样可以做到重用
        Phone.prototype.call = function(){
            console.log("我可以打电话!!");
        }

        //实例化对象
        let Huawei = new Phone('华为', 5999);
        Huawei.call();
        Huawei.sing();
        console.log(Huawei);


        //es6的写法,通过class
        class Shouji{
            //构造方法 constructor名字不能修改,是固定的,我们通过
            //new + 类名 的时候就会自动执行实例对象constructor方法
            constructor(brand, price){
                this.brand = brand;
                this.price = price;
            }

            //方法必须使用该语法, 不能使用 ES5 的对象完整形式,例如call: function(){}
            call(){
                console.log("我可以打电话!!");
            }
        }
        let onePlus = new Shouji("1+", 1999);
        
        console.log(onePlus);
    </script>
</body>
</html>

image-20200721163633557

class静态成员(static标注)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>静态成员</title>
</head>
<body>
    <script>
        function Phone(){

        }
        //我们直接在函数对象上添加属性和方法,但是要注意这是属于函数对象的,它并不属于实例对象
        Phone.name = '手机';
        Phone.change = function(){
            console.log("我可以改变世界");
        }
        Phone.prototype.size = '5.5inch';

        let nokia = new Phone();
        //输出为undefined,我们不能使用Phone上直接添加的属性和方法
        console.log(nokia.name);
        // nokia.change();//报错,没有这个方法
        //不会报错,输出为 5.5inch ,我们通过给Phone函数的原型对象添加方法
        console.log(nokia.size);

        class Phone2{
            //静态属性
            //static标注的属性和方法,它属于类而不属于实例对象
            static name = '手机';
            static change(){
                console.log("我可以改变世界");
            }
        }

        let nokia2 = new Phone2();
        console.log(nokia2.name);//undefined
        console.log(Phone2.name);//手机
    </script>
</body>
</html>
es5构造函数继承
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>对象继承</title>
</head>
<body>
    <script>
        //手机
        function Phone(brand, price){
            this.brand = brand;
            this.price = price;
        }

        Phone.prototype.call = function(){
            console.log("我可以打电话");
        }

        //智能手机
        function SmartPhone(brand, price, color, size){
            //通过call改变Phone中this值,让这个this指向SmartPhone中的this,
            //也就是SmartPhone的实例对象
            Phone.call(this, brand, price);
            this.color = color;
            this.size = size;
        }

        //设置子级构造函数的原型
        //SmartPhone的实例对象上就会存在Phone上的方法
        SmartPhone.prototype = new Phone;
        //矫正,让constructor重新指向SmartPhone
        SmartPhone.prototype.constructor = SmartPhone;

        //声明子类的方法
        SmartPhone.prototype.photo = function(){
            console.log("我可以拍照")
        }

        SmartPhone.prototype.playGame = function(){
            console.log("我可以玩游戏");
        }

        const chuizi = new SmartPhone('锤子',2499,'黑色','5.5inch');

        console.log(chuizi);

    </script>
</body>
</html>

image-20200721170139744

es6中class的继承
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>类继承-2</title>
</head>
<body>
    <script>
        class Phone{
            //构造方法
            constructor(brand, price){
                this.brand = brand;
                this.price = price;
            }
            //父类的成员属性
            call(){
                console.log("我可以打电话!!");
            }
        }

        class SmartPhone extends Phone {
            //构造方法
            constructor(brand, price, color, size){
                // super表示父类的constructor的方法
                
                super(brand, price);
                this.color = color;//子类属性的初始化
                this.size = size;
            }

            photo(){
                console.log("拍照");
            }

            playGame(){
                console.log("玩游戏");
            }
            // 子类对父类方法的重写
            call(){
                //我们想调用父类的同名方法,这样写是错误的,在普通的成员方法
                //里不可以出现super去调用父类的同名方法
                // super();
                console.log('我可以进行视频通话');
            }
        }

        const xiaomi = new SmartPhone('小米',799,'黑色','4.7inch');
        console.log(xiaomi);
        xiaomi.call();
        xiaomi.photo();
        xiaomi.playGame();
    </script>
</body>
</html>

image-20200721170634660

class中的getter和setter设置
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>get 和 set</title>
</head>
<body>
    <script>
        // get 和 set  ,es5中可以对对象的属性进行方法的绑定,
        //当对某个属性进行获取时执行get这个函数,当对某个属性进行设置时执行set这个函数
        class Phone{
            get price(){
                console.log("价格属性被读取了");
                //如果不加返回值,则输出为undefined,因为函数默认的返回值为undefined
                return 'iloveyou';
            }

            // set必须要有一个参数,没有会报错
            set price(newVal){
                console.log('价格属性被修改了');
            }
        }

        //实例化对象
        let s = new Phone();

        console.log(s.price);

        s.price = 'free';
    </script>
</body>
</html>

image-20200721172019948

2.16. 数值扩展

2.16.1. 二进制和八进制

ES6 提供了二进制和八进制数值的新的写法,分别用前缀 0b 和 0o 表示。

2.16.2. Number.isFinite() 与 Number.isNaN()

Number.isFinite() 用来检查一个数值是否为有限的

Number.isNaN() 用来检查一个值是否为 NaN

2.16.3. Number.parseInt() 与 Number.parseFloat()

ES6 将全局方法 parseInt 和 parseFloat,移植到 Number 对象上面,使用不变。

2.16.4. Math.trunc

用于去除一个数的小数部分,返回整数部分。

2.16.5. Number.isInteger

Number.isInteger() 用来判断一个数值是否为整数

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>数值扩展</title>
</head>
<body>
    <script>
        //0. Number.EPSILON 是 JavaScript 表示的最小精度
        //EPSILON 属性的值接近于 2.2204460492503130808472633361816E-16,
        //在js中,如果两个数的差值小于这个,则认为这两个数相等
        function equal(a, b){
            // Math.abs(a-b)表示求差值的绝对值
            if(Math.abs(a-b) < Number.EPSILON){
                return true;
            }else{
                return false;
            }
        }
        console.log(0.1 + 0.2);//浮点运算,实际计算的值为 0.30000000000000004
        console.log(0.1 + 0.2 === 0.3);//false
        console.log(equal(0.1 + 0.2, 0.3))//true

        //1. 二进制和八进制
        let b = 0b1010;//二进制
        let o = 0o777;//八进制
        let d = 100;//十进制
        let x = 0xff;//十六进制

        console.log(b);//10
        console.log(o);//511
        console.log(d);//100
        console.log(x);//255

        //2. Number.isFinite  检测一个数值是否为有限数
        console.log(Number.isFinite(100));//true
        console.log(Number.isFinite(100/0));//false
        //false, Infinity表示无穷,分为正无穷+Infinity和负无穷-Infinity
        console.log(Number.isFinite(Infinity));
        
        //3. Number.isNaN 检测一个数值是否为 NaN 
        // 在es5中 isNaN 是一个单独的函数,在es6中作为了number的一个方法
        console.log(Number.isNaN(123)); //false

        //4. Number.parseInt Number.parseFloat字符串转整数
        console.log(Number.parseInt('5211314love'));//5211314
        console.log(Number.parseFloat('3.1415926神奇'));//3.1415926

        //5. Number.isInteger 判断一个数是否为整数
        console.log(Number.isInteger(5));//true
        console.log(Number.isInteger(2.5));//false

        //6. Math.trunc 将数字的小数部分抹掉  
        console.log(Math.trunc(3.5));//3

        //7. Math.sign 判断一个数到底为正数 负数 还是零,如果是正数返回1,0返回0,负数返回-1
        console.log(Math.sign(100));//1
        console.log(Math.sign(0));//0
        console.log(Math.sign(-20000));//-1

    </script>
</body>
</html>

2.17. 对象方法扩展

ES6 新增了一些 Object 对象的方法

	1) Object.is 比较两个值是否严格相等,与『===』行为基本一致(+0NaN2) Object.assign 对象的合并,将源对象的所有可枚举属性,复制到目标对象

	3) __proto__、setPrototypeOf、 setPrototypeOf 可以直接设置对象的原型

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>对象方法扩展</title>
</head>
<body>
    <script>
        //1. Object.is 判断两个值是否完全相等,它跟全等有些类似,但是要注意NaN的比较是不同的 
        console.log(Object.is(120, 120));//true
        console.log(Object.is(NaN, NaN));//true 
        console.log(NaN === NaN);//false,NaN跟任何一个做是否相等比较,返回结果都为false,

        //2. Object.assign 对象的合并
        const config1 = {
            host: 'localhost',
            port: 3306,
            name: 'root',
            pass: 'root',
            test: 'test'
        };
        const config2 = {
            host: 'http://atguigu.com',
            port: 33060,
            name: 'atguigu.com',
            pass: 'iloveyou',
            test2: 'test2'
        }
        //后面的会把前面的所覆盖掉,但是要注意只是覆盖重名的,不重名的不会覆盖
        console.log(Object.assign(config1, config2));

        //3. Object.setPrototypeOf 设置原型对象,
        //不推荐使用这样设置原型  Object.getPrototypeof 获取原型对象
        const school = {
            name: '尚硅谷'
        }
        const cities = {
            xiaoqu: ['北京','上海','深圳']
        }
        Object.setPrototypeOf(school, cities);//为school对象设置原型
        console.log(Object.getPrototypeOf(school));//获取原型
        console.log(school);
    </script>
</body>
</html>

image-20200721212620574

2.18. 模块化

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

2.18.1. 模块化的好处

模块化的优势有以下几点: 1) 防止命名冲突 2) 代码复用 3) 高维护性

2.18.2. 模块化规范产品
ES6 之前的模块化规范有:es6之前js没有模块化 

	1) CommonJS => NodeJS、Browserify 

	2) AMD => requireJS 

	3) CMD => seaJS
2.18.3. ES6 模块化语法
模块功能主要由两个命令构成:exportimportexport 命令用于规定模块的对外接口  
    
	import 命令用于输入其他模块提供的功能

代码演示:

m1.js
//这种写法称为 分别暴露
export let school = '尚硅谷';

export function teach() {
    console.log("我们可以教给你开发技能");
}
m2.js
let school = '尚硅谷';

function findJob(){
    console.log("我们可以帮助你找工作!!");
}

//统一暴露
export {school, findJob};
m3.js
//默认暴露
export default {
    school: 'ATGUIGU',
    change: function(){
        console.log("我们可以改变你!!");
    }
}
app.js
//入口文件

//模块引入
import * as m1 from "./m1.js";
import * as m2 from "./m2.js";
import * as m3 from "./m3.js";

// console.log(m1);
// console.log(m2);
// console.log(m3);

// m1.teach();
// m2.findJob();
// m3.default.change();

//此部分对应home.html
//修改背景颜色为粉色
// es6导入 npm 包的语法,此次导入jQuery的包,下载jQuery为 npm i jquery
import $ from 'jquery';// const $ = require("jquery");
$('body').css('background','pink');
index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ES6 模块化</title>
</head>
<body>
    <!-- 以前的写法 -->
    <!-- <script src="./src/js/m1.js"></script> -->
    
    <script type="module">

        //第一种导入方式: 通用的导入方式
        //引入 m1.js 模块内容
        //引入m1.js这个模块中所有暴露的数据(前面加了export的)存入m1这个变量中
        import * as m1 from "./src/js/m1.js"; 
        console.log(m1);

        // 引入 m2.js 模块内容
        import * as m2 from "./src/js/m2.js";
        console.log(m2);

        // 引入 m3.js 
        import * as m3 from "./src/js/m3.js";
        console.log(m3);
        m3.default.change();//默认暴露要加上 default 调用

        //第二种导入方式: 解构赋值形式
        //我们可以直接使用这两个变量
        import {school, teach} from "./src/js/m1.js";
        console.log(school,teach);
        
        //如果js模块的命名在引入时重名了,我们直接 as 命名
        import {school as guigu, findJob} from "./src/js/m2.js";
        console.log(guigu);

        //引入默认暴露时必须对default进行重命名
        import {default as m33} from "./src/js/m3.js";
        console.log(m33);

        //第三种导入方式: 简便形式  只能针对默认暴露
        import m333 from "./src/js/m3.js";//直接写变量
        console.log(m333);
    </script>
    <!-- 像上面这样一个一个的引入太过于繁琐,我们可以直接设置一个js入口文件,
         并在其中引入所有的js文件,然后再在html文档中引入这个js文件 -->
    <!-- <script src="./src/js/app.js" type="module"></script> -->
</body>
</html>
home.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <!-- 
        1. 安装工具 npm i babel-cli babel-preset-env browserify(webpack) -D
        2. 编译 npx babel src/js -d dist/js --presets=babel-preset-env
        3. 打包 npx browserify dist/js/app.js -o dist/bundle.js

        babel-cli           是babel命令行的一个工具
        babel-preset-env    将最新的es语法转换为es5的语法
        browserify          打包工具
        -D                  表示局部安装, -g 为全局安装,如果为全局安装就不用再前面加 npx 了
        -o                  后面的表示输出路径
     -->
     <script src="dist/bundle.js"></script>
</body>
</html>

2.19 补充

a.js

export const name = 'jack';
export const age = 18;

b.js

// 引入a.js的所有内容并重命名为a , a 实际上是一个对象,有name属性和age属性
import * as a from './a';

// 把a对象再作为对象暴露出去
export { a };

index.js

import * as b from './b';

// 拿到name的值
console.log(b.a.name);  // jack

【ES6】export default 和 export 区别

export default 和 export 区别:

1.export与export default均可用于导出常量、函数、文件、模块等
    
2.你可以在其它文件或模块中通过import + (常量 | 函数 | 文件 | 模块)名的方式,将其导入,以便能够对其进行使用
    
3.在一个文件或模块中,export、import可以有多个,export default仅有一个
    
4.通过export方式导出,在导入时要加{ },export default则不需要
1.export

//a.js
export const str = "blablabla~";
export function log(sth) { 
  return sth;
}
对应的导入方式:

//b.js
import { str, log } from 'a'; //也可以分开写两次,导入的时候带花括号

2.export default

//a.js
const str = "blablabla~";
export default str;
对应的导入方式:

//b.js
import str from 'a'; //导入的时候没有花括号

使用export default命令,为模块指定默认输出,这样就不需要知道所要加载模块的变量名

//a.js
let sex = "boy";
export default sex(sex不能加大括号{})

原本直接export sex外部是无法识别的,加上default就可以了.但是一个文件内最多只能有一个export default。

其实此处相当于为sex变量值"boy"起了一个系统默认的变量名default,自然default只能有一个值,
所以一个文件内不能有多个export default
// b.js
本质上,a.js文件的export default输出一个叫做default的变量,然后系统允许你为它取任意名字。
所以可以为import的模块起任何变量名,且不需要用大括号包含

import any from "./a.js"
import any12 from "./a.js" 
console.log(any,any12)   // boy,boy

【ES6】var、let、const的区别

ECMAScript 和 JavaScript 到底是什么关系?

	ECMAScript是一个国际通过的标准化脚本语言。JavaScript由ECMAScript和DOM、BOM三者组成。
	可以简单理解为:ECMAScript是JavaScript的语言规范,JavaScript是ECMAScript的实现和扩展。

    2011 年,ECMAScript 5.1 版发布。之前我们大部分人用的也就是ES5

    2015 年 6 月,ECMAScript 6 正式通过,成为国际标准。

1. 块级作用域 {}

​ ES5 中作用域有:全局作用域、函数作用域。没有块作用域的概念。

​ ES6 中新增了块级作用域。块作用域由{ }包括,if语句和 for语句里面的{ }也属于块作用域。

<script type="text/javascript">	
    {
        var a = 1;
        console.log(a); // 1
    }
     // 通过var定义的变量可以跨块作用域访问到。
    console.log(a); // 1
 
    (function A() {
        var b = 2;
        console.log(b); // 2
    })();
	// 可见,通过var定义的变量不能跨函数作用域访问到
    // console.log(b); // 报错,
    
 	// if语句和for语句中用var定义的变量可以在外面访问到,
    // 可见,if语句和for语句属于块作用域,不属于函数作用域。 
    if(true) {
        var c = 3;
    }
    console.log(c); // 3
    for(var i = 0; i < 4; i ++) {
        var d = 5;
    };
    console.log(i); // 4   (循环结束i已经是4,所以此处i为4)
    console.log(d); // 5
</script>

2. var、let、const的区别

  1. var定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问。
  2. let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。
  3. const用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改。但是变量的值依旧可以改变。
const person = {
	name:"xiaowang"
}

person.name="laowang" //依旧可以修改
person = {} //报错,不能被重新赋值
<script type="text/javascript">
    // 块作用域
    {
        var a = 1;
        let b = 2;
        const c = 3;
        // c = 4; // 报错,不能修改
        var aa;
        let bb;
        // const cc; // 报错,必须赋值
        console.log(a); // 1
        console.log(b); // 2
        console.log(c); // 3
        console.log(aa); // undefined
        console.log(bb); // undefined
    }
    console.log(a); // 1
    // 报错,let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。
    // console.log(b); 
    // 报错,const用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改。
    // console.log(c); 
 
    // 函数作用域
    (function A() {
        var d = 5;
        let e = 6;
        const f = 7;
        console.log(d); // 5
        console.log(e); // 6  
        console.log(f); // 7 
 
    })();
    // 报错,var定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问。
    // console.log(d); 
    // 报错,let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。
    // console.log(e); 
    // 报错,const用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改。
    // console.log(f); 
</script>

3. const定义的对象属性是否可以改变

 const person = {
     name : 'jiuke',
     sex : '男'
 }
 
 person.name = 'test'
 
 console.log(person.name)  // test

运行上述代码,发现person对象的name属性确实被修改了,这是怎么回事呢?

因为对象是引用类型的,person中保存的仅是对象的指针,这就意味着,const仅保证指针不发生改变,修改对象的属性不会改变对象的指针,所以是被允许的。也就是说const定义的引用类型只要指针不发生改变,其他的不论如何改变都是允许的。

然后我们试着修改一下指针,让person指向一个新对象,果然报错

const person = {
   name : 'jiuke',
   sex : '男'
}
// 报错 
person = {
   name : 'test',
   sex : '男'
}

源码获取

至此,我们的JavaScript进阶(四)就讲解完成了。下篇将基于尚硅谷2020版Web前端ES6教程,涵盖ES6-ES11进行讲解ES7、ES8、ES9、ES10、ES11新特性源码素材可以通过关注我的微信公众号我爱学习呀嘻嘻 ,回复关键字JavaScript进阶源码素材进行获取哦。

JavaScript进阶(五):ES7、ES8、ES9、ES10、ES11新特性

JavaScript进阶(一):类、对象、构造函数、原型对象、继承

JavaScript进阶(二):函数、this、严格模式、高阶函数、闭包、递归

JavaScript进阶(三):正则表达式、ES6语法、ES6内置对象

JavaScript入门链接:

JavaScript入门(一):JavaScript基础知识

JavaScript入门(二):结合代码讲解①

JavaScript入门(三):结合代码讲解②

JavaScript入门(四):结合代码讲解③

JavaScript入门(五):结合代码讲解④

JavaScript入门(六):结合代码讲解⑤

image-20211108230322493

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值