1.概述
- ECMAScript、JavaScript、NodeJs,它们的区别是什么?
ECMAScript:简称ES,是一个语言标准(循环、判断、变量、数组等数据类型)
JavaScript:运行在浏览器端的语言,该语言使用ES标准。 ES + web api = JavaScript
NodeJs:运行在服务器端的语言,该语言使用ES标准。 ES + node api = JavaScript
无论JavaScript,还是NodeJs,它们都是ES的超集(super set即包含但是还有自己的东西)
2.块级绑定
2-1声明变量的问题
使用var声明变量
-
允许重复的变量声明:导致数据被覆盖
-
变量提升:怪异的数据访问、闭包问题
怪异数据访问
-
全局变量挂载到全局对象:全局对象成员污染问题,即容易覆盖window已有成员
2-2使用let声明变量
ES6不仅引入let关键字用于解决变量声明的问题,同时引入了块级作用域的概念
块级作用域:代码执行时遇到花括号,会创建一个块级作用域,花括号结束,销毁块级作用域
声明变量的问题
-
全局变量挂载到全局对象:全局对象成员污染问题
let声明的变量不会挂载到全局对象
-
允许重复的变量声明:导致数据被覆盖
let声明的变量,不允许当前作用域范围内重复声明
-
允许在不同作用域使用
-
变量提升:怪异的数据访问、闭包问题
使用let不会有变量提升,因此,不能在定义let变量之前使用它
底层实现上,let 声明的变量实际上也会有提升,但是,提升后会将其放入到“暂时性死区”,如果访问的变量位于暂时性死区,则会报错:“Cannot access 'a' before initialization”。当代码运行到该变量的声明语句时,会将其从暂时性死区中移除。
在循环中,用 let 声明的循环变量,会特殊处理,每次进入循环体,都会开启一个新的作用域,并且将循环变量绑定到该作用域(每次循环,使用的是一个全新的循环变量)
在循环中使用 let 声明的循环变量,在循环结束后会销毁
块级作用域演示
1let a = 123; //全局作用域定义a
2{
3 let a = 456; //块级作用域定义a
4 console.log(a); //使用的是块级作用域中的a
5}
6console.log(a)
7//在块级作用域中用let定义的变量,在作用域外不能访问。(里面能用外面,外面不能用里面)
2-3 使用const声明变量
const和let完全相同,仅在于用const声明的变量,必须在声明时赋值,而且不可以重新赋值(赋相同的值也不行),即常量。
实际上,在开发中,应该尽量使用 const 来声明变量,以保证变量的值不会随意篡改,原因如下:
-
根据经验,开发中的很多变量,都是不会更改,也不应该更改的。
-
后续的很多框架或者是第三方 JS 库,都要求数据不可变,使用常量可以一定程度上保证这一点。
注意的细节:
-
常量不可变,是指声明的常量的内存空间不可变,并不保证内存空间中的地址指向的其他空间不可变。
-
常量的命名
1.特殊的常量:该常量从字面意义上,一定是不可变的,比如圆周率、月地距地或其他一些绝不可能变化的配置。通常,该常量的名称全部使用大写,多个单词之间用下划线分割
普通的常量:使用和之前一样的命名即可
- 在 for 循环中,循环变量不可以使用常量,for in 循环可以用常量
3.字符串和正则表达式
3-1更好的Unicode支持
早期,由于存储空间宝贵,Unicode 使用 16 位二进制来存储文字。我们将一个 16 位的二进制编码叫做一个码元(Code Unit)。
后来,由于技术的发展,Unicode 对文字编码进行了扩展,将某些文字扩展到了 32 位(占用两个码元),并且,将某个文字对应的二进制数字叫做码点(Code Point)。
ES6 为了解决这个困扰,为字符串提供了方法:codePointAt,根据字符串码元的位置得到其码点。
同时,ES6 为正则表达式添加了一个 flag: u,如果添加了该配置,则匹配时,使用码点匹配
1const text = "𠮷"; //占用了2个码元(32位)
2
3console.log("字符串长度:", text.length);//2
4console.log("使用正则测试:", /^.$/.test(text));//false正则不能匹配
5console.log("使用正则测试:", /^.$/u.test(text));//true匹配码点
6console.log("得到第一个码元:", text.charCodeAt(0));
7console.log("得到第二个码元:", text.charCodeAt(1));
8
9//𠮷:𠮷
10console.log("得到第一个码点:", text.codePointAt(0));
11console.log("得到第二个码点:", text.codePointAt(1));
判断字符串char,是32位,还是16位
1function is32bit(char, i) {
2 //如果码点大于了16位二进制的最大值,则其是32位的
3 return char.codePointAt(i) > 0xffff;
4}
得到一个字符串码点的真实长度
1function getLengthOfCodePoint(str) {
2 var len = 0;
3 for (let i = 0; i < str.length; i++) {
4 //i在索引码元
5 if (is32bit(str, i)) {
6 //当前字符串,在i这个位置,占用了两个码元
7 i++;
8 }
9 len++;
10 }
11 return len;
12}
13
14console.log("𠮷是否是32位的:", is32bit("𠮷", 0))
15console.log("ab𠮷ab的码点长度:", getLengthOfCodePoint("ab𠮷ab"))
3-2更多的字符串API
以下均为字符串的实例(原型)方法
includes判断字符串中是否包含指定的子字符串
startsWith判断字符串中是否以指定的字符串开始
endsWith判断字符串中是否以指定的字符串结尾
repeat将字符串重复指定的次数,然后返回一个新字符串。
1const text = "成哥是狠人";
2
3console.log("是否包含“狠”:", text.includes("狠"));
4console.log("是否包含“狠”:", text.includes("狠",3));//下标3开始查找
5console.log("是否以“成哥”开头:", text.startsWith("成哥"));
6console.log("是否以“狠人”结尾:", text.endsWith("狠人"));
7console.log("重复4次:", text.repeat(4));
3-3【拓展】 正则中的粘连标记
标记名:y
含义:匹配时,完全按照正则对象中的lastIndex位置开始匹配,并且匹配的位置必须在lastIndex位置。
1const text = "Hello World!!!";
2const reg = /W\w+/y;//W开头,任意单词字符一次或多次
3reg.lastIndex = 3;
4console.log("reg.lastIndex:", reg.lastIndex);
5console.log(reg.test(text));
3-4 模板字符串
ES6 之前处理字符串繁琐的两个方面:
- 多行字符串
写法一
1var test = "邓哥喜欢秋葵\
2邓哥也喜欢韭菜";
写法二
1var test = [
2 "邓哥喜欢秋葵",
3 "邓哥也喜欢韭菜"
4].join("\n");
以上,多行字符串的处理比较繁琐
- 字符串拼接
在 ES6 中,提供了模板字符串的书写,可以非常方便的换行和拼接,要做的,仅仅是将字符串的开始或结尾改为 `符号
1var test =`邓哥喜欢秋葵
2邓哥喜欢韭菜`;
如果要在字符串中拼接 js 表达式,只需要在模板字符串中使用`${JS 表达式}
1var love1 = "秋葵";
2var love2 = "香菜";
3var text = `邓哥喜欢${love1}
4邓哥也喜欢${love2}
5表达式可以是任何有意义的数据${1 + 3 * 2 / 0.5}
6表达式是可以嵌套的:${`表达式中的模板字符串${love1 + love2}`}
7\n\n
8奥布瓦的发顺丰
9在模板字符串中使用\${ JS表达式 } 可以进行插值
10`;
11console.log(text);
3-5【拓展】模板字符串标记
1var love1 = "秋葵";
2var love2 = "香菜";
3var text = myTag`邓哥喜欢${love1},邓哥也喜欢${love2}。`;
相当于
1text = myTag(["邓哥喜欢", ",邓哥也喜欢", "。"], "秋葵", "香菜")
模拟模板字符串功能
1function myTag(parts) {
2 const values = Array.prototype.slice.apply(arguments).slice(1);
3 // parts.length = value.length + 1
4 let str = "";
5 for (let i = 0; i < values.length; i++) {
6 str += `${parts[i]}:${values[i]}`;
7 if (i === values.length - 1) {
8 str += parts[i + 1];
9 }
10 }
11 return str;
12}
13console.log(text);
String.raw
1var text = String.raw`abc\t\nbcd`;//告诉他没有特殊字符,完全打印
2console.log(text);
演示
1<!DOCTYPE html>
2<html lang="en">
3
4 <head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <meta http-equiv="X-UA-Compatible" content="ie=edge">
8 <title>Document</title>
9 </head>
10
11 <body>
12 <p>
13 <textarea id="txt"></textarea>多行文本框
14 <button id="btn">设置div的内容</button>
15 </p>
16 <div id="container"></div>
17 <script>
18 const container = document.getElementById("container");
19 const txt = document.getElementById("txt");
20 const btn = document.getElementById("btn");
21
22 btn.onclick = function () {
23 container.innerHTML = safe`<p>
24${txt.value}
25<!--这里的safe是为了防止用户传入恶意的代码-->
26 </p>
27<h1>
28${txt.value}
29 </h1>
30`;
31 }
32 function safe(parts) {
33 const values = Array.prototype.slice.apply(arguments).slice(1);
34 let str = "";
35 for (let i = 0; i < values.length; i++) {
36 const v = values[i].replace(/</g, "<").replace(/>/g, ">");
37 str += parts[i] + v;
38 if (i === values.length - 1) {
39 str += parts[i + 1];
40 }
41 }
42 return str;
43 }
44 </script>
45 </body>
46
47</html>
总结
在模板字符串书写之前,可以加上标记:
标记是一个函数,函数参数如下:
-
参数 1:被插值分割的字符串数组
-
后续参数:所有的插值
4.函数
4-1参数默认值
使用
原来的写法
1function sum(){
2 b = b === undefined && 1;
3 c = c === undefined && 2;
4 return a+b+c;
5}
6console.log(sum(10));
在书写形参时,直接给形参赋值,附的值即为默认值
这样一来,当调用函数时,如果没有给对应的参数赋值(给它的值是 undefined),则会自动使用默认值。
1function sum(a,b=1,c=2){
2 return a+b+c;
3}
4console.log(sum(10));//相当于console.log(sum(10,undefined,undefined))
5console.log(sum(11,null,undefined));//就不对了,null为0
6console.log(sum(1,undefined,5))//1+1+5=7
演示
1// 面试题
2function getContainer() {
3 console.log("abc");//问abc运行几次-------------------2次,因为两次都用了默认值
4 return document.getElementById("container");
5}
6
7/**
8 * 创建一个元素
9 * @param {*} name 元素的名称
10 * @param {*} container 元素的父元素
11 * @param {*} content 元素的内容
12 */
13function createElement(name = "div", container = getContainer(), content = "") {
14 const ele = document.createElement(name)
15 if (content) {
16 ele.innerHTML = content;
17 }
18 container.appendChild(ele);
19}
20createElement(undefined, undefined, "手动阀手动阀十分")//调用一次
21createElement(undefined, undefined, "234242342424")//调用一次
22createElement(undefined, document.getElementById("container"), "234242342424")//传值了,不调用了
[扩展]对 arguments 的影响
只要给函数加上参数默认值,该函数会自动变量严格模式下的规则:arguments 和形参脱离
1function test(a, b) {
2console.log("arugments", arguments[0], arguments[1]);//两个形参 1 2
3console.log("a:", a, "b:", b);//1 2
4a = 3;
5console.log("arugments", arguments[0], arguments[1]);// 3 2
6console.log("a:", a, "b:", b);// 3 2
7}
8test(1, 2);
9// 当加上参数默认值
10function test(a, b=1) {
11console.log("arugments", arguments[0], arguments[1]);//两个形参 1 2
12console.log("a:", a, "b:", b);//1 2
13a = 3;
14console.log("arugments", arguments[0], arguments[1]);// 3 2
15console.log("a:", a, "b:", b);// 3 2
16}
17test(1, 2);
[扩展]留意暂时性死区
形参和 ES6 中的 let 或 const 声明一样,具有作用域,并且根据参数的声明顺序,存在暂时性死区。
1function test(a, b=a) {
2 console.log(a, b);//都有值,不会用参数默认值,1,2
3}
4test(1, 2);
5
6function test(a, b=a) {
7 console.log(a, b);//参数默认值:1,1
8}
9test(1);
10
11function test(a = b, b) {
12 console.log(a, b);/没有使用参数默认值:1,2
13}
14test(1, 2);
15
16function test(a = b, b) {
17 console.log(a, b);//Cannot access 'b' before initialization
18}
19test(undefined, 2);
4-2剩余参数
旧方法
1function sum(args){
2 let sum = 0;
3 for(let i = 0; i < args.length; i++){
4 sum += args[i];
5 }
6 return sum;
7}
8console.log(sum([1]));//必须传数组
升级
1function sum(){
2 let sum = 0;
3 for(let i = 0; i < arguments.length; i++){
4 sum += arguments[i];
5 }
6 return sum;
7}
8console.log(sum(1));//必须传数组
arguments 的缺陷:
-
如果和形参配合使用,容易导致混乱
-
从语义上,使用 arguments 获取参数,由于形参缺失,无法从函数定义上理解函数的真实意图
ES6 的剩余参数专门用于收集末尾的所有参数,将其放置到一个形参数组中。
语法:
细节
-
一个函数,仅能出现一个剩余参数
-
一个函数,如果有剩余参数,剩余参数必须是最后一个参数
演示1
1function sum(...args) {
2 //args收集了所有的参数,形成的一个数组
3 let sum = 0;
4 for (let i = 0; i < args.length; i++) {
5 sum += args[i];
6 }
7 return sum;
8}
9console.log(sum())
10console.log(sum(1))
11console.log(sum(1, 2))
12console.log(sum(1, 2, 3))
演示2
1function test(...args1, ...args2) {
2 console.log(args1)
3 console.log(args2)
4}
5test(1, 32, 46, 7, 34);
演示3
1function test(a, b, ...args) {
2}
3test(1, 32, 46, 7, 34);
4-3展开运算符
使用方式:
对所有数字求和
1function sum(...args) {
2 let sum = 0;
3 for (let i = 0; i < args.length; i++) {
4 sum += args[i];
5 }
6 return sum;
7}
获取一个指定长度的随机数组成的数组
1function getRandomNumbers(length) {
2 const arr = [];
3 for (let i = 0; i < length; i++) {
4 arr.push(Math.random());
5 }
6 return arr;
7}
8const numbers = getRandomNumbers(10);
9//希望将数组的每一项展开,依次作为参数传递,而不是把整个数组作为一个参数传递
10// 这就要用到展开运算符
11// sum(numbers) 这是传了一个参数
12console.log(sum(...numbers))//相当于传递了10个参数
13console.log(sum(1, 3, ...numbers, 3, 5))
展开数组:克隆
1const arr1 = [3, 67, 8, 5];
2
3//克隆arr1数组到arr2
4
5// const arr2 = [0, ...arr1, 1];相加别的数
6const arr2 = [...arr1];
7
8console.log(arr2, arr1 === arr2)//false
展开对象:浅克隆
1const obj1 = {
2 name: "成哥",
3 age: 18,
4 love: "邓嫂",
5 address: {
6 country: "中国",
7 province: "黑龙江",
8 city: "哈尔滨"
9 }
10}
11// 浅克隆到obj2
12const obj2 = {
13 ...obj1,
14 name: "邓哥"//邓哥可以覆盖
15};
16console.log(obj2)
17console.log(obj1.address === obj2.address)//true
深克隆
1const obj1 = {
2 name: "成哥",
3 age: 18,
4 loves: ["邓嫂", "成嫂1", "成嫂2"],
5 address: {
6 country: "中国",
7 province: "黑龙江",
8 city: "哈尔滨"
9 }
10}
11// 深克隆到obj2
12const obj2 = {
13 ...obj1,
14 name: "邓哥",
15 address: {//address深克隆
16 ...obj1.address
17 },
18 loves: [...obj1.loves, "成嫂3"]//浅克隆
19};
20console.log(obj2)
21console.log(obj1.loves === obj2.loves)
22console.log(obj1.address === obj2.address)
4-4剩余参数和展开运算符练习
maxmin
1<!DOCTYPE html>
2<html lang="en">
3
4 <head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <meta http-equiv="X-UA-Compatible" content="ie=edge">
8 <title>Document</title>
9 </head>
10
11 <body>
12 <div>
13 <p><input type="number" name="" id="" value="0"></p>
14 <p><input type="number" name="" id="" value="0"></p>
15 <p><input type="number" name="" id="" value="0"></p>
16 <p><input type="number" name="" id="" value="0"></p>
17 <p><input type="number" name="" id="" value="0"></p>
18 <p><input type="number" name="" id="" value="0"></p>
19 <p><input type="number" name="" id="" value="0"></p>
20 <p><input type="number" name="" id="" value="0"></p>
21 <p><input type="number" name="" id="" value="0"></p>
22 <p><input type="number" name="" id="" value="0"></p>
23 </div>
24 <p>
25 <button>计算</button>
26 </p>
27 <p>
28 <h2>最大值:<span id="spanmax"></span></h2>
29 <h2>最小值:<span id="spanmin"></span></h2>
30 </p>
31 <script>
32 function getValues() {
33 const numbers = [];
34 const inps = document.querySelectorAll("input")
35 for (let i = 0; i < inps.length; i++) {
36 numbers.push(+inps[i].value)//+表示转换成数字
37 }
38 return numbers;
39 }
40
41 const btn = document.querySelector("button")
42
43 btn.onclick = function () {
44 const numbers = getValues(); //得到文本框中的所有数字形成的数组
45 spanmax.innerText = Math.max(...numbers)
46 spanmin.innerText = Math.min(...numbers)
47 //老方法spanmax.innerText =Math.max.apply(null,numbers);this绑定null
48 }
49 </script>
50 </body>
51</html>
curry
1<!DOCTYPE html>
2<html lang="en">
3
4 <head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <meta http-equiv="X-UA-Compatible" content="ie=edge">
8 <title>Document</title>
9 </head>
10
11 <body>
12 <script>
13 function cal(a, b, c, d) {
14 return a + b * c - d;
15 }
16 //curry:柯里化,用户固定某个函数的前面的参数,得到一个新的函数,新的函数调用时,接收剩余的参数
17 function curry(func, ...args) {//后面不管几个
18 return function (...subArgs) {//新的函数也不知道几个
19 const allArgs = [...args, ...subArgs];
20 if (allArgs.length >= func.length) {
21 //参数够了
22 return func(...allArgs);
23 } else {
24 //参数不够,继续固定
25 return curry(func, ...allArgs);
26 }
27 }
28 }
29
30 const newCal = curry(cal, 1, 2)
31
32 console.log(newCal(3, 4)) // 1+2*3-4
33 console.log(newCal(4, 5)) // 1+2*4-5
34 console.log(newCal(5, 6)) // 1+2*5-6
35 console.log(newCal(6, 7)) // 1+2*6-7
36
37 const newCal2 = newCal(8)
38
39 console.log(newCal2(9)); // 1+2*8-9
40 </script>
41 </body>
42
43</html>
demo
1function test(a, b, c) {
2 console.log(a, b, c);
3}
4test(2, 6, 7);
5const arr = ["asf", "Gfh", "111"];
6//以前apply
7test(...arr);
4-5 明确函数的双重用途
ES6提供了一个特殊的API,可以使用该API在函数内部,判断该函数是否使用了new来调用
1new.target
2//该表达式,得到的是:如果没有使用new来调用函数,则返回undefined
3//如果使用new调用函数,则得到的是new关键字后面的函数本身
普通函数与构造函数的使用
1function Person(firstName, lastName) {
2 // 判断是否是使用new的方式来调用的函数
3 // 过去的判断方式
4 // if (!(this instanceof Person)) {
5 // throw new Error("该函数没有使用new来调用")
6 // }
7 if (new.target === undefined) {
8 throw new Error("该函数没有使用new来调用")
9 }
10 this.firstName = firstName;
11 this.lastName = lastName;
12 this.fullName = `${firstName} ${lastName}`;
13}
14const p1 = new Person("袁", "进");//构造函数 有效
15console.log(p1)
16
17const p2 = Person("袁", "进");//直接调用 无效
18console.log(p2);
19// 强行绑定this会绕开过去的判断方式
20const p3 = Person.call(p1, "袁", "进")
21console.log(p3);
4-6箭头函数
回顾:this 指向
- 通过对象调用函数,this 指向对象
- 直接调用函数,this 指向全局对象
- 如果通过 new 调用函数,this 指向新创建的对象
- 如果通过 apply、call、bind 调用函数,this 指向指定的数据
- 如果是 DOM 事件函数,this 指向事件源
常见问题
1<!DOCTYPE html>
2<head>
3 <title></title>
4</head>
5<body>
6
7 <script>
8 const obj = {
9 count: 0,
10 start: function () {
11 // obj调用的,所以this->obj
12 console.log(this)// obj对象,这里this没问题
13 setInterval(function(){
14 console.log(this);//这里的this出了问题,JS引擎内部调用,直接调用的,this全局window
15 // this.count++;undefined++==NAN
16 // console.log(this.count);
17 },1000)
18 },
19 regEvent: function(){
20 window.onclick = function(){
21 console.log(this.count);//事件,this指向事件源window
22 }
23 }
24 }
25 obj.start();
26 obj.regEvent();
27 </script>
28</body>
29</html>
解决方案:闭包
1<!DOCTYPE html>
2<head>
3 <title></title>
4
5</head>
6<body>
7
8 <script>
9 const obj = {
10 count: 0,
11 start: function () {
12 // this->obj
13 var _this = this;
14 setInterval(function(){
15 console.log(this);
16 _this.count++;
17 console.log(_this.count);
18 },1000)
19 },
20 regEvent: function(){
21 var _this = this;
22 window.onclick = function(){
23 console.log(_this.count);
24 }
25 }
26
27 }
28 obj.start();
29 obj.regEvent();
30
31 </script>
32</body>
33</html>
使用语法
箭头函数是一个函数表达式,理论上,任何使用函数表达式的场景都可以使用箭头函数
完整语法:
1(参数1, 参数2, ...)=>{
2 //函数体
3}
上述代码简化成
1const obj = {
2 count: 0,
3 start: function () {
4 setInterval(() => {
5 this.count++;
6 console.log(this.count);
7 }, 1000)
8 },
9 regEvent: function () {
10 window.onclick = () => {
11 console.log(this.count);
12 }
13 },
14 print: function () {
15 console.log(this)
16 console.log(this.count)
17 }
18}
19
20// obj.start();
21// obj.regEvent();
22obj.print();
23//不过,问题也随之解决了
24//原因:箭头函数的函数体中的 this,取决于箭头函数定义的位置的 this 指向,而与如何调用无关
如果参数只有一个,可以省略小括号
1const print = num => {
2 console.log("给我的数字是:", num)
3}
4print(2);
如果箭头函数只有一条返回语句,可以省略大括号,和 return 关键字
判断一个数是不是奇数
1const isOdd = function (num) {
2 return num % 2 !== 0;
3}
优化
1const isOdd = (num) => {
2 return num % 2 !== 0;
3}
优化
1const isOdd = num => num % 2 !== 0;
2
3console.log(isOdd(3))
4console.log(isOdd(4))
注意细节
-箭头函数的函数体中的 this,取决于箭头函数定义的位置的 this 指向,而与如何调用无关
箭头函数没有原型,所以箭头函数不能作用构造函数使用
应用场景
-
临时性使用的函数,并不会刻意调用它,比如:
- 事件处理函数
- 异步处理函数(定时器函数)
- 其他临时性的函数
-
为了绑定外层 this 的函数
-
在不影响其他代码的情况下,保持代码的简洁,最常见的,数组方法中的回调函数
小现象
1const obj = {
2 count : 0,
3 print : function (){
4 console.log(this);
5 console.log(this.count);
6 }
7 // 相当于
8 // print : this,
9 // this没有在函数里面,就是window;
10}
11const print = obj.print;
12print();//window undefined
箭头函数能解决吗
1const obj = {
2 count : 0,
3 print :() =>{
4 console.log(this);
5 console.log(this.count);
6 }
7}
8const print = obj.print;
9// console.log(print());//还是undefined
这种问题通常不解决,开发中都是遇到函数里面套函数出现this指向问题,没有这种问题
对象的属性不用箭头函数
演示
1const sum = (a, b) => {
2 return {
3 a: a,
4 b: b,
5 sum: a + b
6 }
7};
8console.log(sum(3, 5))
优化
1const sum = (a, b) => ({
2 a: a,
3 b: b,
4 sum: a + b
5});
6console.log(sum(3, 5))
演示
1const func = () => {
2 console.log(this)//window
3}
演示
1const func = () => {
2 console.log(this)//自己没有this,只能从外面找,是window
3 console.log(arguments)//也没有arguments
4}
5
6const obj = {
7 method:func
8}
9obj.method(234);
演示:面试题
1const func = () => {
2 console.log(this)
3}
4
5const obj = {
6 method: function () {
7 const func = () => {
8 console.log(this)//箭头函数没有this,往外看,function的this,this由obj调用,就是obj
9 console.log(arguments)//箭头函数没有arguments,往外看,传的是234
10 }
11 func()
12 }
13}
14obj.method(234);
演示 在不影响其他代码的情况下,保持代码的简洁,最常见的,数组方法中的回调函数
1const numbers = [3, 7, 78, 3, 5, 345];
2//以前的方法const result = numbers.map(function(num){return num*2})
3const result = numbers.filter(num => num % 2 !== 0).map(num => num * 2).reduce((a, b) => a + b)
4console.log(result);
5.对象
5-1新增的对象字面量语法
- 成员速写
如果对象字面量初始化时,成员的名称来自于一个变量,并且和变量的名称相同,则可以进行简写
1function createUser(loginId, loginPwd, nickName){
2 return {
3 loginId : loginId,
4 loginPwd : loginPwd,
5 nickName : nickName,
6 id : Math.random()
7 }
8}
9console.log(createUser("abc"),"13","aaa")
- 成员速写
1function createUser(loginId, loginPwd, nickName){
2 return {
3 loginId,
4 loginPwd,
5 nickName,
6 id : Math.random()
7 }
8}
9console.log(createUser("abc"),"13","aaa")
演示
1function createUser(loginId, loginPwd, nickName) {
2 const sayHello = function () {//这里不能用箭头函数,没有this,就调用外面的window了
3 console.log("loginId", this.loginId, "nickname", this.nickName)
4 }
5 return {
6 loginId,
7 loginPwd,
8 nickName,
9 sayHello,
10 id: Math.random()
11 }
12}
13const u = createUser("abc", "123", "aaa");
14u.sayHello();
- 方法速写
对象字面初始化时,方法可以省略冒号和function关键字
1const user = {
2 name: "姬成",
3 age: 100,
4 sayHello : function(){
5 console.log(this.name, this.age)
6 }
7}
8user.sayHello();
1const user = {
2 name: "姬成",
3 age: 100,
4 sayHello(){
5 console.log(this.name, this.age)
6 }
7}
8user.sayHello();
- 计算属性名
有的时候,初始化对象时,某些属性名可能来自于某个表达式的值,在ES6,可以使用中括号来表示该属性名是通过计算得到的。
1const prop1 = "name2";
2const prop2 = "age2";
3const prop3 = "sayHello2";
4// 以前必须user[prop1] = '姬成'
5const user = {
6 [prop1]: "姬成",
7 [prop2]: 100,
8 [prop3](){
9 console.log(this[prop1], this[prop2])
10 }
11}
12user[prop3]();
5-2Object的新增API
Object是函数(构造函数)
-
Object.is
用于判断两个数据是否相等,基本上跟严格相等(===)是一致的,除了以下两点:
(1)NaN 和 NaN相等
(2)+0 和-0 不相等
1console.log(NaN === NaN); // false
2console.log(+0 === -0); // true
3
4console.log(Object.is(NaN, NaN))// true
5console.log(Object.is(+0, -0))// false
-
Object.assign 后面覆盖前面
用于混合对象
1const obj1 = {
2 a: 123,
3 b: 456,
4 c: "abc"
5}
6
7const obj2 = {
8 a: 789,
9 d: "kkk"
10}
1/*
2想这样
3{
4 a: 789,
5 b: 456,
6 c: "abc",
7 d: "kkk"
8}
9*/
ES7做法:
1const obj = {
2 ...obj1,
3 ...obj2 //后面覆盖前面
4}
5console.log(obj);
Object.assign做法:
将obj2的数据,覆盖到obj1,并且会对obj1产生改动,然后返回obj1。对obj1改动了
1const obj = Object.assign(obj1, obj2);
防止产生影响
1const obj = Object.assign({}, obj1, obj2);// 只改动了{},没有改动obj1,obj2
- Object.getOwnPropertyNames 的枚举顺序
Object.getOwnPropertyNames 方法之前就存在,只不过,官方没有明确要求,对属性的顺序如何排序,如何排序,完全由浏览器厂商决定。
ES6 规定了该方法返回的数组的排序方式如下:
先排数字,并按照升序排序
再排其他,按照书写顺序排序
1const obj = {
2 d: 1,
3 b: 2,
4 a: 3,
5 0: 6,
6 5: 2,
7 4: 1
8}
9const props = Object.getOwnPropertyNames(obj)//返回字符串数组
10console.log(props)
- Object.setPrototypeOf
该函数用于设置某个对象的隐式原型
比如: Object.setPrototypeOf(obj1, obj2),
相当于: obj1.__proto__ = obj2
演示
1const obj1 = {
2 a: 1
3}
4const obj2 = {
5 b: 2
6}
7
8// obj1.__proto__ = obj2
9
10Object.setPrototypeOf(obj1, obj2)
11
12console.log(obj1)
5-3面向对象简介
面向对象:一种编程思想,跟具体的语言
对比面向过程:
-
面向过程:思考的切入点是功能的步骤
-
面向对象:思考的切入点是对象的划分
【大象装冰箱】
面向过程(小工程)
1//1. 冰箱门打开
2function openFrige(){
3}
4openFrige();
5//2. 大象装进去
6function elephantIn(){
7}
8elephantIn();
9//3. 冰箱门关上
10function closeFrige(){
11}
12closeFrige();
面向对象(大型工程)
1/**
2 * 大象
3 */
4function Elephant() {
5
6}
7/**
8 * 冰箱
9 */
10function Frige() {
11}
12Frige.prototype.openDoor = function () {
13}
14Frige.prototype.closeDoor = function () {
15}
16Frige.prototype.join = function(something){
17 this.openDoor();
18 //装东西
19
20 this.closeDoor();
21}
22//1. 冰箱门打开
23// var frig = new Frige();
24// frig.openDoor();
25// //2. 大象装进去
26// var ele = new Elephant();
27// frig.join(ele);
28// //3. 冰箱门关上
29// frig.closeDoor();
30var frig = new Frige();
31frig.join(new Elephant());
5-4类:构造函数的语法糖
传统的构造函数的问题
- 属性和原型方法定义分离,降低了可读性
- 原型成员可以被枚举,但是不希望枚举
- 默认情况下,构造函数仍然可以被当作普通函数使用
面向对象中,将下面对一个对象的所有成员的定义,统称为类
1//构造函数 构造器
2function Animal(type, name, age, sex) {
3 this.type = type;
4 this.name = name;
5 this.age = age;
6 this.sex = sex;
7}
8// 中间存在1万行代码
9//定义实例方法(原型方法)
10Animal.prototype.print = function () {
11 console.log(`【种类】:${this.type}`);
12 console.log(`【名字】:${this.name}`);
13 console.log(`【年龄】:${this.age}`);
14 console.log(`【性别】:${this.sex}`);
15}
16
17const a = new Animal("狗", "旺财", 3, "男");
18a.print();
19for (const prop in a) {//枚举
20 console.log(prop)// 也把print(原型上的东西也遍历出来了,这是不希望的)
21}
类的特点
- 类声明不会被提升,与 let 和 const 一样,存在暂时性死区
- 类中的所有代码均在严格模式下执行
- 类的所有方法都是不可枚举的
- 类的所有方法都无法被当作构造函数使用
- 类的构造器必须使用 new 来调用
1class Animal {
2 constructor(type, name, age, sex) {
3 this.type = type;
4 this.name = name;
5 this.age = age;
6 this.sex = sex;
7 }
8 print() {
9 console.log(`【种类】:${this.type}`);
10 console.log(`【名字】:${this.name}`);
11 console.log(`【年龄】:${this.age}`);
12 console.log(`【性别】:${this.sex}`);
13 }
14}
15
16const a = new Animal("狗", "旺财", 3, "男");
17const a = Animal("狗", "旺财", 3, "男");//error不能当普通函数调用
18const p = new a.print();//error 类的所有方法都无法被当作构造函数使用
19a.print();
20console.log(a);
21
22for (const prop in a) {
23 console.log(prop)//不会遍历原形了
24}
5-5类的其他书写方式
-
可计算的成员名
1const printName = "print";
2
3class Animal {
4 constructor(type, name, age, sex) {
5 this.type = type;
6 this.name = name;
7 this.age = age;
8 this.sex = sex;
9 }
10
11 [printName]() {
12 console.log(`【种类】:${this.type}`);
13 console.log(`【名字】:${this.name}`);
14 console.log(`【年龄】:${this.age}`);
15 console.log(`【性别】:${this.sex}`);
16 }
17}
18
19const a = new Animal("狗", "旺财", 3, "男");
20a[printName]();
- getter和setter
Object.defineProperty 可定义某个对象成员属性的读取和设置
使用getter和setter控制的属性,不在原型上
对年龄限制:
1const printName = "print";
2
3class Animal {
4 constructor(age) {
5 this.setAge(age);
6 this.sex = sex;
7 }
8 getAge() {
9 return this._age + "岁";
10 }
11 setAge(age) {
12 if (age < 0) {
13 age = 0;
14 }
15 else if (age > 1000) {
16 age = 1000;
17 }
18 this._age = age;
19 }
20
21 [printName]() {
22 console.log(`【年龄】:${this.age}`);
23 }
24}
25var a = new Animal("狗", "旺财", 3, "男");
升级
1const printName = "print";
2
3class Animal {
4 constructor(type, name, age, sex) {
5 this.type = type;
6 this.name = name;
7 this.age = age;
8 this.sex = sex;
9 }
10 //创建一个age属性,并给它加上getter,读取该属性时,会运行该函数
11 get age() {// 不能给参数
12 return this._age + "岁";
13 }
14 //创建一个age属性,并给它加上setter,给该属性赋值时,会运行该函数
15 set age(age) {
16 if (typeof age !== "number") {
17 throw new TypeError("age property must be a number");
18 }
19 if (age < 0) {
20 age = 0;
21 }
22 else if (age > 1000) {
23 age = 1000;
24 }
25 this._age = age;
26 }
27
28 [printName]() {
29 console.log(`【种类】:${this.type}`);
30 console.log(`【名字】:${this.name}`);
31 console.log(`【年龄】:${this.age}`);
32 console.log(`【性别】:${this.sex}`);
33 }
34}
35var a = new Animal("狗", "旺财", 3, "男");
- 静态成员
构造函数本身的成员。Animal.abc
使用static关键字定义的成员即静态成员
函数本身也是对象
象棋demo:不需要创建旗子也知道宽高。宽高不应该作为实例属性
1class Chess {
2 constructor(name) {
3 this.name = name;
4 }
5 static width = 50;
6 static height = 50;
7 static method() {
8 }
9}
10console.log(Chess.width);
11console.log(Chess.height);
12Chess.method();
-
字段初始化器(ES7)
1class Test {
2 static a = 1;// a在Test.a里面
3// a = 1;
4b = 2;//实例成员
5c = 3;
6constructor() {
7 // 相当于
8 // b = 2;
9 // c = 3;
10 this.d = this.b + this.c;
11 // d=5
12}
13}
14const t = new Test();
15console.log(t)
注意:
1). 使用static的字段初始化器,添加的是静态成员
2). 没有使用static的字段初始化器,添加的成员位于对象上
3). 箭头函数在字段初始化器位置上,指向当前对象
1class Test {
2 // 这里相当于a = 123;
3 constructor() {
4 this.a = 123;
5 }
6 print(){
7 console.log(this.a)
8 }
9}
10const t = new Test();
11t.print()//123
为了绑定方法里面的this,让this始终指向当前对象,防止有人这样做
1class Test {
2 constructor() {
3 this.a = 123;
4 }
5 print(){
6 console.log(this.a)// 严格模式this->undefined
7 }
8}
9const t = new Test();
10const p = t.print();
11p();// this--> undefiend
利用字段初始化器,print赋值为一个箭头函数。箭头函数在字段初始化器位置上,指向当前对象
1class Test {
2 constructor() {
3 this.a = 123;
4 }
5 print = () => {
6 console.log(this.a)// this指向当前对象。因为字段初始化器相当于在构造函数前写上this.print=()=>{console.log(this.a)}
7 }
8}
9
10const t = new Test();
11const p = t.print;
12p();//123
现在的print不在原形上了,现在在对象上
1class Test {
2 constructor() {
3 this.a = 123;
4 }
5 print = () => {
6 console.log(this.a)
7 }
8}
9
10const t1 = new Test();
11const t2 = new Test();
12console.log(t1.print === t2.print);// false 每一个对象有一个自己的print
- 类表达式
1const A = class { //匿名类,类表达式
2 a = 1;
3b = 2;
4}
5
6const a = new A();
7console.log(a)
- [扩展]装饰器(ES7)(Decorator)
还没成为正式标准,浏览器不支持
横切关注点
装饰器的本质是一个函数
1class Test {
2 @Obsolete// 标记已过期
3 print() {
4 console.log("print方法")
5 }
6}
7function Obsolete(target, methodName, descriptor) {// 装饰器的本质是一个函数
8 // 分别会输出:
9 // function Test
10 // print
11 // { value: function print(){}, ... }
12 // console.log(target, methodName, descriptor);
13 const oldFunc = descriptor.value
14 descriptor.value = function (...args) {
15 console.warn(`${methodName}方法已过时`);
16 oldFunc.apply(this, args);
17 }
18}
5-6类的继承
如果两个类 A 和 B,如果可以描述为:B 是 A,则,A 和 B 形成继承关系
如果 B 是 A,则:
- B 继承自 A
- A 派生 B
- B 是 A 的子类
- A 是 B 的父类
如果 A 是 B 的父类,则 B 会自动拥有 A 中的所有实例成员。
这样狗里面没有print,要实现继承,必须Dog_prototype--->Animal prototype
老方法:
1function Animal(type, name, age, sex) {
2 this.type = type;
3 this.name = name;
4 this.age = age;
5 this.sex = sex;
6}
7Animal.prototype.print = function () {
8 console.log(`【种类】:${this.type}`);
9 console.log(`【名字】:${this.name}`);
10 console.log(`【年龄】:${this.age}`);
11 console.log(`【性别】:${this.sex}`);
12}
13
14// 以前
15function Dog(name, age, sex) {
16 //借用父类的构造函数
17 Animal.call(this, "犬类", name, age, sex);
18}
19Object.setPrototypeOf(Dog.prototype, Animal.prototype);
20// 把Dog.prototype的隐式原形设置成Animal.prototype的
21const d = new Dog("旺财", 3, "公");
22d.print();
23console.log(d);
新的关键字:
-
extends:继承,用于类的定义
-
super
-
直接当作函数调用,表示父类构造函数
-
如果当作对象使用,则表示父类的原型
注意:ES6 要求,如果定义了 constructor,并且该类是子类,则必须在 constructor 的第一行手动调用父类的构造函数
如果子类不写 constructor,则会有默认的构造器,该构造器需要的参数和父类一致,并且自动调用父类构造器
1class Animal {
2 constructor(type, name, age, sex) {
3 this.type = type;
4 this.name = name;
5 this.age = age;
6 this.sex = sex;
7 }
8 print() {
9 console.log(`【种类】:${this.type}`);
10 console.log(`【名字】:${this.name}`);
11 console.log(`【年龄】:${this.age}`);
12 console.log(`【性别】:${this.sex}`);
13 }
14 jiao() {
15 throw new Error("动物怎么叫的?");
16 }
17}
18class Dog extends Animal {//翻译:狗继承自animal
19 constructor(name, age, sex) {
20 super("犬类", name, age, sex);// Animal,就不用Animal.call了
21 this.loves = "吃骨头"; // 子类特有的属性,父类不知道
22 }
23 print() {
24 super.print(); //其他模块想调用父类的print
25 //自己特有的代码
26 console.log(`【爱好】:${this.loves}`);
27 }
28 jiao() {//同名方法,会覆盖父类
29 console.log("旺旺!");
30 }
31}
32const d = new Dog("旺财", 3, "公");
33d.print();
34console.log(d)// 原形
35d.jiao();
【冷知识】
1class Animal {
2 constructor(type, name, age, sex) {
3 if (new.target === Animal) {
4 throw new TypeError("你不能直接创建Animal的对象,应该通过子类创建")
5 }
6 this.type = type;
7 this.name = name;
8 this.age = age;
9 this.sex = sex;
10 }
11 print() {
12 console.log(`【种类】:${this.type}`);
13 console.log(`【名字】:${this.name}`);
14 console.log(`【年龄】:${this.age}`);
15 console.log(`【性别】:${this.sex}`);
16 }
17 jiao() {
18 throw new Error("动物怎么叫的?");
19 }
20}
21class Dog extends Animal {
22 constructor(name, age, sex) {
23 super("犬类", name, age, sex);
24 // 子类特有的属性
25 this.loves = "吃骨头";
26 }
27 print() {
28 //调用父类的print
29 super.print();
30 //自己特有的代码
31 console.log(`【爱好】:${this.loves}`);
32 }
33 //同名方法,会覆盖父类
34 jiao() {
35 console.log("旺旺!");
36 }
37}
38//下面的代码逻辑有误
39const a = new Dog("旺财", 3, "公")
40a.print();
es5和es6继承方式的区别
ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加
到this.上(Parent.apply(this)) .
ES6的继承机制完全不同,实质上是先创建父类的实例对象this (所以必
须先调用父类的super()方法),然后再用子类的构造函数修改this。
ES5的继承时通过原型或构造函数机制来实现。
ES6通过class关键字定义类,里面有构造方法,类之间通过extends关
键字实现继承。
子类必须在constructor方法中调用super方法,否则新建实例报错。因
为子类没有自己的this对象,而是继承了父类的this对象,然后对其进行加工。
如果不调用supe
per
方法,子类得不到this对象。
注意super
关键字指代父类的实例,即父类的this 对象。
注意:在子类构造函数中,调用super后,才可使用this 关键字,否则
报错。
6.解构
6-1对象解构
什么是解构
使用 ES6 的一种语法规则,将一个对象或数组的某个属性提取到某个变量中
解构不会对被解构的目标造成任何影响
1const user = {
2 name: "kevin",
3 age: 11,
4 sex: "男",
5 address: {
6 province: "四川",
7 city: "成都"
8 }
9}
10// 以前
11// let name, age, sex, address;
12// name = user.name;
13// age = user.age;
14// sex = user.sex;
15// address = user.address;
16// 有了解构
17写法1:
18let name, age, sex, address, abc;
19({ name, age, sex, address } = user);
20写法2:
21let {name,age,sex,address} = user;
在解构中使用默认值
demo
1// 先定义5个变量,然后从对象中读取同名属性,放到变量中
2let { name, age, sex, address, abc = 123 } = user
3console.log(name, age, sex, address, abc)// 如果abc不赋值,就是undefined
非同名属性解构
demo
1const user = {
2 name: "kevin",
3 age: 11,
4 sex: "男",
5 address: {
6 province: "四川",
7 city: "成都"
8 }
9}
10// 先定义4个变量:name、age、gender、address
11// 再从对象user中读取同名属性赋值(其中gender读取的是sex属性)
12let { name, age, sex: gender = 123, address } = user
13
14console.log(name, age, gender, address)
深入解构
1const user = {
2 name: "kevin",
3 age: 11,
4 sex: "男",
5 address: {
6 province: "四川",
7 city: "成都"
8 }
9}
10//解构出user中的name、province
11//定义两个变量name、province
12//再解构
13const { name, address: { province } } = user;
14console.log(name, address, province)// address is not defined。对address进一步解构,不会当变量
6-2数组解构
数组本质是对象
对象大括号解构,数组中括号
演示1
1const numbers = ["a", "b", "c", "d"];
2const {
3 0: n1,
4 1: n2
5} = numbers;
6console.log(n1, n2)
继续简化
1let n1, n2;
2([n1, n2] = numbers);
等价于
1const [n1, n2] = numbers;
按需取值
1const numbers = ["a", "b", "c", "d"];
2const [n1, , , n4, n5 = 123] = numbers;
3console.log(n1, n4, n5)
带有嵌套:得到numbers下标为4的数组中的下标为2的数据,放到变量n中
1const numbers = ["a", "b", "c", "d", [1, 2, 3, 4]];
2const [, , , , [, , n]] = numbers;
3console.log(n)
得到numbers下标为4的数组的属性a,赋值给变量A
1const numbers = ["a", "b", "c", "d", {
2 a: 1,
3 b: 2
4}];
5// const [,,,,{a}] = numbers;
6// const [, , , , { a: A }] = numbers;
7const { a: A } = numbers[4];//把对象单独拿出来
8console.log(A)
解构出name,然后,剩余的所有属性,放到一个新的对象中,变量名为obj
1const user = {
2 name: "kevin",
3 age: 11,
4 sex: "男",
5 address: {
6 province: "四川",
7 city: "成都"
8 }
9}
10// name: kevin
11// obj : {age:11, sex:"男", address:{...}}
12const { name, ...obj } = user;
13console.log(name, obj)
得到数组前两项,分别放到变量a和b中,然后剩余的所有数据放到数组nums
1const numbers = [324, 7, 23, 5, 3243];
2const [a, b, ...nums] = numbers;
3// 以前:const a = numbers[0], b = numbers[1], nums = numbers.slice(2);
4console.log(a, b, nums);
交换两个变量
1let a = 1, b = 2;
2[b, a] = [a, b]//右边数组,赋值,[1,2]; 左边解构
3console.log(a, b)
试题
1const article = {
2 title: "文章标题",
3 content: "文章内容",
4 comments: [{
5 content: "评论1",
6 user: {
7 id: 1,
8 name: "用户名1"
9 }
10 }, {
11 content: "评论2",
12 user: {
13 id: 2,
14 name: "用户名2"
15 }
16 }]
17}
解构出第二条评论的用户名和评论内容
name:"用户名2" content:"评论2"
写法1
1const {
2 comments: [, {
3 content,
4 user: {
5 name
6 }
7 }]
8} = article;
9console.log(content,name)
写法2
1const [, {
2 content,
3 user: {
4 name
5 }
6}] = article.comments;//先把数组项取出来
7console.log(content,name)
写法3
1const {
2 content,
3 user: {
4 name
5 }
6} = article.comments[1]
7
8console.log(content, name)
6-3参数解构
目前的问题:要写无数的user . 太麻烦
1function print(user) {
2 console.log(`姓名:${user.name}`)
3 console.log(`年龄:${user.age}`)
4 console.log(`性别:${user.sex}`)
5 console.log(`身份:${user.address.province}`)
6 console.log(`城市:${user.address.city}`)
7}
8const user = {
9 name: "kevin",
10 age: 11,
11 sex: "男",
12 address: {
13 province: "四川",
14 city: "成都"
15 }
16}
17print(user)
直接解构
1function print({ name, age, sex, address: {
2 province,
3 city
4} }) {
5 console.log(`姓名:${name}`)
6 console.log(`年龄:${age}`)
7 console.log(`性别:${sex}`)
8 console.log(`身份:${province}`)
9 console.log(`城市:${city}`)
10}
11
12const user = {
13 name: "kevin",
14 age: 11,
15 sex: "男",
16 address: {
17 province: "四川",
18 city: "成都"
19 }
20}
21print(user)
ajax默认值配置
1function ajax(options) {
2 const defaultOptions = {//默认配置
3 method: "get",//默认值
4 url: "/"
5 }
6 const opt = {
7 ...defaultOptions,
8 ...options
9 }
10 console.log(opt)
11}
12ajax({
13 url:"/abc",
14 method:"post",//不传的话默认get
15})
解构简化
1function ajax({
2 method = "get",
3 url = "/"
4} = {}) {// 需要加上={},参数默认值
5 console.log(method, url)
6}
7ajax()//不传递则undefined无法解构,so需要加上={},参数默认值
8ajax({
9 url:"/abc",
10})
7.符号
7-1普通符号
符号是ES6新增的一个数据类型,它通过使用函数 Symbol(符号描述) 来创建
创建一个符号
1const syb1 = Symbol();
2const syb2 = Symbol("abc");//描述信息
3console.log(syb1, syb2);
英雄攻击实例:里面的随机数没必要展示在外面:大道至简
1const hero = {
2 attack: 30,
3 hp: 300,
4 defence: 10,
5 gongji() { //攻击
6 //伤害:攻击力*随机数(0.8~1.1)
7 const dmg = this.attack * this.getRandom(0.8, 1.1);
8 console.log(dmg);
9 },
10 getRandom(min, max) { //根据最小值和最大值产生一个随机数,这个方法没必要展示
11 return Math.random() * (max - min) + min;
12 }
13}
优化
1class Hero {
2 constructor(attack, hp, defence) {
3 this.attack = attack;
4 this.hp = hp;
5 this.defence = defence;
6 }
7
8 gongji() {
9 //伤害:攻击力*随机数(0.8~1.1)
10 const dmg = this.attack * this.getRandom(0.8, 1.1);
11 console.log(dmg);
12 }
13
14 getRandom(min, max) { //根据最小值和最大值产生一个随机数
15 return Math.random() * (max - min) + min;
16 }
17}
符号设计的初衷,是为了给对象设置私有属性
私有属性:只能在对象内部使用,外面无法使用
符号具有以下特点:
-
没有字面量:(字面量是直接书写出来)
-
使用 typeof 得到的类型是 symbol
1//创建一个符号
2const syb1 = Symbol();
3const syb2 = Symbol("abc");
4console.log(syb1, syb2);
5console.log(typeof syb1 === "symbol", typeof syb2 === "symbol")
1//创建一个符号
2
3const syb1 = Symbol("这是随便写的一个符号");//描述信息
4const syb2 = Symbol("这是随便写的一个符号");
5
6console.log(syb1, syb2);
7console.log(syb1 === syb2)//false
1const syb1 = Symbol("这是用于对象的一个属性");
2
3const obj = {
4 a: 1,
5 b: 2,
6 [syb1]: 3 //符号属性
7}
8
9console.log(obj);
-
开发者可以通过精心的设计,让这些属性无法通过常规方式被外界访问
1const hero = (function () {
2 const getRandom = Symbol();
3
4 return {
5 attack: 30,
6 hp: 300,
7 defence: 10,
8 gongji() { //攻击
9 //伤害:攻击力*随机数(0.8~1.1)
10 const dmg = this.attack * this[getRandom](0.8, 1.1);
11 console.log(dmg);
12 },
13 [getRandom](min, max) { //根据最小值和最大值产生一个随机数
14 return Math.random() * (max - min) + min;
15 }
16 }
17})()
18
19console.log(hero);
类的写法
1const Hero = (() => {
2 const getRandom = Symbol();
3
4 return class {
5 constructor(attack, hp, defence) {
6 this.attack = attack;
7 this.hp = hp;
8 this.defence = defence;
9 }
10
11 gongji() {
12 //伤害:攻击力*随机数(0.8~1.1)
13 const dmg = this.attack * this[getRandom](0.8, 1.1);
14 console.log(dmg);
15 }
16
17 [getRandom](min, max) { //根据最小值和最大值产生一个随机数
18 return Math.random() * (max - min) + min;
19 }
20 }
21})();
22
23const h = new Hero(3, 6, 3);
24console.log(h);
-
符号属性是不能枚举的,因此在 for-in 循环中无法读取到符号属性,Object.keys 方法也无法读取到符号属性
1const syb = Symbol();
2const obj = {
3 [syb]: 1,
4 a: 2,
5 b: 3
6}
7for (const prop in obj) {
8 console.log(prop)//只能输出a,b
9}
10console.log(Object.keys(obj))
11console.log(Object.getOwnPropertyNames(obj))
12//得到的是一个符号属性的数组
13const sybs = Object.getOwnPropertySymbols(obj);
14console.log(sybs, sybs[0] === syb)
-
Object.getOwnPropertyNames 尽管可以得到所有无法枚举的属性,但是仍然无法读取到符号属性
-
ES6 新增 Object.getOwnPropertySymbols 方法,可以读取符号
非常规方式读取符号
1const Hero = (() => {
2 const getRandom = Symbol();
3
4 return class {
5 constructor(attack, hp, defence) {
6 this.attack = attack;
7 this.hp = hp;
8 this.defence = defence;
9 }
10
11 gongji() {
12 //伤害:攻击力*随机数(0.8~1.1)
13 const dmg = this.attack * this[getRandom](0.8, 1.1);
14 console.log(dmg);
15 }
16
17 [getRandom](min, max) { //根据最小值和最大值产生一个随机数
18 return Math.random() * (max - min) + min;
19 }
20 }
21})();
22
23const h = new Hero(3, 6, 3);
24const sybs = Object.getOwnPropertySymbols(Hero.prototype);
25const prop = sybs[0];
26console.log(h[prop](3, 5))
1const syb = Symbol();
2console.log(syb*2);
3console.log(syb+'2');
显示转换
1const syb = Symbol();
2const str = String(syb);
3console.log(str);
7-2共享符号
根据某个符号名称(符号描述)能够得到同一个符号
1Symbol.for("符号名/符号描述") //获取共享符号
应用
1const syb1 = Symbol.for("abc");
2const syb2 = Symbol.for("abc");
3都不填符号或都填一样的都是true
4console.log(syb1 === syb2)
5const obj1 = {
6 a: 1,
7 b: 2,
8 [syb1]: 3
9}
10
11const obj2 = {
12 a: "a",
13 b: "b",
14 [syb2]: "c"
15}
16
17console.log(obj1, obj2);
应用
1const obj = {
2 a: 1,
3 b: 2,
4 [Symbol.for("c")]: 3
5}
6console.log(obj[Symbol.for("c")]);
实现共享符号底层原理
1const SymbolFor = (() => {
2 const global = {};//用于记录有哪些共享符号
3 return function (name) {
4 console.log(global)
5 if (!global[name]) {
6 global[name] = Symbol(name);
7 }
8 console.log(global);
9 return global[name];
10 }
11})();
12
13const syb1 = SymbolFor("abc");
14
15const syb2 = SymbolFor("abc");
16
17console.log(syb1 === syb2);
7-3知名**(公共、具名)**符号
知名符号是一些具有特殊含义的共享符号,通过 Symbol 的静态属性得到
ES6 延续了 ES5 的思想:减少魔法,暴露内部实现!
因此,ES6 用知名符号暴露了某些场景的内部实现
-
Symbol.hasInstance
该符号用于定义构造函数的静态成员,它将影响 instanceof 的判定
1obj instanceof A
2//等效于
3A[Symbol.hasInstance](obj) // Function.prototype[Symbol.hasInstance]
演示参与改动
1function A() {
2
3}
4Object.defineProperty(A, Symbol.hasInstance, {//更改A的Symbol.hasInstance属性
5 value: function (obj) {
6 return false;
7 }
8})
9const obj = new A();
10console.log(obj instanceof A);
11//内部实现 console.log(A[Symbol.hasInstance](obj));
-
[扩展] Symbol.isConcatSpreadable
该知名符号会影响数组的 concat 方法
1const arr = [3];
2const result = arr.concat(56, arr2)
3/*两种可能*/
4// [3, 56, [5,6,7,8]]
5// [3, 56, 5, 6, 7, 8]结果是分割处理
6console.log(result)
要想不分割
1const arr = [3];
2const arr2 = [5, 6, 7, 8];
3arr2[Symbol.isConcatSpreadable] = false;
4const result = arr.concat(56, arr2)
5console.log(result)
以此,也可以应用到对象
1const arr = [1];
2const obj = {
3 0: 3,
4 1: 4,
5 length: 2,
6 [Symbol.isConcatSpreadable]: true
7}
8const result = arr.concat(2, obj)
9console.log(result)
-
[扩展] Symbol.toPrimitive
1var obj = {
2 a:1,
3 b:2
4}
5// 先调用valueOf后toString
6console.log(obj.valueOf());//拿不到基本类型
7console.log(obj.toString())//可以拿到基本类型
8console.log(obj + 1)
更改内部实现
1var obj = {
2 a:1,
3 b:2
4}
5obj[Symbol.toPrimitive] = function () {
6 return 2;
7}
8console.log(obj + 1)
该知名符号会影响类型转换的结果
1class Temperature {
2 constructor(degree) {
3 this.degree = degree;
4 }
5
6 [Symbol.toPrimitive](type) {
7 if (type === "default") {
8 return this.degree + "摄氏度";
9 }
10 else if (type === "number") {
11 return this.degree;
12 }
13 else if (type === "string") {
14 return this.degree + "℃";
15 }
16 }
17}
18
19const t = new Temperature(30);
20
21console.log(t + "!");
22console.log(t / 2);
23console.log(String(t));
-
[扩展] Symbol.toStringTag
该知名符号会影响 Object.prototype.toString 的返回值
1class Person {
2 [Symbol.toStringTag] = "Person"
3}
4
5const p = new Person();
6
7const arr = [32424, 45654, 32]
8
9console.log(Object.prototype.toString.apply(p));
10console.log(Object.prototype.toString.apply(arr))
-
其他知名符号
8. 异步处理
本节整理异步处理相关知识点。
9.FetchApi
9-1. Fetch Api 概述
XMLHttpRequest的问题
- 所有的功能全部集中在同一个对象上,容易书写出混乱不易维护的代码
- 采用传统的事件驱动模式,无法适配新的 Promise Api
Fetch Api 的特点
- 并非取代 AJAX,而是对 AJAX 传统 API 的改进
- 精细的功能分割:头部信息、请求信息、响应信息等均分布到不同的对象,更利于处理各种复杂的 AJAX 场景
- 使用 Promise Api,更利于异步代码的书写
- Fetch Api 并非 ES6 的内容,属于 HTML5 新增的 Web Api
- 需要掌握网络通信的知识
9-2基本使用
使用 fetch 函数即可立即向服务器发送网络请求
参数
该函数有两个参数:
- 必填,字符串,请求地址
- 选填,对象,请求配置
• method:字符串,请求方法,默认值 GET
• headers:对象,请求头信息
• body: 请求体的内容,必须匹配请求头中的 Content-Type
• mode:字符串,请求模式
• cors:默认值,配置为该值,会在请求头中加入 origin 和 referer。默认允许跨域
• no-cors:配置为该值,不会在请求头中加入 origin 和 referer,跨域的时候可能会出现问题
• same-origin:指示请求必须在同一个域中发生,如果请求其他域,则会报错
• credentials: 如何携带凭据(cookie)
• omit:默认值,不携带 cookie
• same-origin:请求同源地址时携带 cookie
• include:请求任何地址都携带 cookie
• cache:配置缓存模式
• default: 表示 fetch 请求之前将检查下 http 的缓存.
• no-store: 表示 fetch 请求将完全忽略 http 缓存的存在. 这意味着请求之前将不再检查下 http 的缓存, 拿到响应后, 它也不会更新 http 缓存.
• no-cache: 如果存在缓存, 那么 fetch 将发送一个条件查询 request 和一个正常的 request, 拿到响应后, 它会更新 http 缓存.
• reload: 表示 fetch 请求之前将忽略 http 缓存的存在, 但是请求拿到响应后, 它将主动更新 http 缓存.
• force-cache: 表示 fetch 请求不顾一切的依赖缓存, 即使缓存过期了, 它依然从缓存中读取. 除非没有任何缓存, 那么它将发送一个正常的 request.
• only-if-cached: 表示 fetch 请求不顾一切的依赖缓存, 即使缓存过期了, 它依然从缓存中读取. 如果没有缓存, 它将抛出网络错误(该设置只在 mode 为”same-origin”时有效).
返回值
ajax是靠回调函数;xhr是靠readStateChange
fetch 函数返回一个 Promise 对象
• 当收到服务器的返回结果后(只要是有结果就行,不管成功还是失败),Promise 进入 resolved 状态,状态数据为 Response 对象
• 当网络发生错误(或其他导致无法完成交互的错误)时,Promise 进入 rejected 状态,状态数据为错误信息
拿到所有省份数据
1async function getPromises() {
2 const url = "http://101.132.72.36:5100/api/local";
3 //GET请求数据在地址栏里面,POST请求数据在请求体里面
4 const config = {
5 method: "GET",
6 headers: {
7 "Content-Type": "application/json"//这里写了json格式,body里面就要用json格式(HTTP协议规定)
8 a: 1,//也可以自定义
9 }
10 }
11 try {
12 const resp = await fetch(url, config)
13 // console.log(resp)
14 const result = await resp.text()// 解析成文本
15 console.log(result)
16 } catch (err) {
17 console.log(err)
18 }
19}
20getPromises()
简化一下
1async function getPromises() {
2 const url = "http://101.132.72.36:5100/api/local";
3 const resp = await fetch(url)
4 const result = await resp.text()
5 console.log(result)
6}
7getPromises()
• ok:boolean,当响应消息码在 200~299 之间时为 true,其他为 false
• status:number,响应的状态码
• text():用于处理文本格式的 Ajax 响应。它从响应中获取文本流,将其读完,然后返回一个被解决为 string 对象的 Promise。(异步的)
• blob():用于处理二进制文件格式(比如图片或者电子表格)的 Ajax 响应。它读取文件的原始数据,一旦读取完整个文件,就返回一个被解决为 blob 对象的 Promise。
• json():用于处理 JSON 格式的 Ajax 的响应。它将 JSON 数据流转换为一个被解决为 JavaScript 对象的 promise。
• redirect():可以用于重定向到另一个 URL。它会创建一个新的 Promise,以解决来自重定向的 URL 的响应。
9-3Request 对象
除了使用基本的fetch方法,还可以通过创建一个Request对象来完成请求(实际上,fetch的内部会帮你创建一个Request对象)
注意点:尽量保证每次请求都是一个新的Request对象
1let req;
2function getRequestInfo() {//得到request对象
3 if (!req) {//没有的话,创建一个
4 const url = "http://101.132.72.36:5100/api/local";
5 req = new Request(url, {});
6 console.log(req);
7 }
8 // 尽量保证每次请求都是一个新的Request对象
9 return req.clone(); //克隆一个全新的request对象,配置一致
10}
11async function getProvinces() {
12 const resp = await fetch(getRequestInfo())
13 const result = await resp.json();
14 console.log(result)
15}
9-4 Response对象
1let req;
2function getRequestInfo() {
3 if (!req) {
4 const url = "http://101.132.72.36:5100/api/local";
5 req = new Request(url, {});
6 console.log(req);
7 }
8 return req.clone(); //克隆一个全新的request对象,配置一致
9}
10async function getProvinces() {
11 // const resp = await fetch(getRequestInfo()) 请求服务器
12 // 模拟服务器相应结果:测试用的数据用Response构建
13 const resp = new Response(`[
14{"id":1, "name":"北京"},
15{"id":2, "name":"天津"}
16]`, {// 配置
17 ok: true,
18 status: 200
19 })
20 const result = await getJSON(resp);
21 console.log(result)
22}
23async function getJSON(resp) {
24 const json = await resp.json();
25 return json;
26}
在Request和Response对象内部,会将传递的请求头对象,转换为Headers
Headers对象中的方法:
- has(key):检查请求头中是否存在指定的key值
- get(key): 得到请求头中对应的key值
- set(key, value):修改对应的键值对。不存在就会新增
- append(key, value):添加对应的键值对。如果添加的一样,就会逗号分隔成两个值
- keys(): 得到所有的请求头键的集合
- values(): 得到所有的请求头中的值的集合
- entries(): 得到所有请求头中的键值对的集合
1let req;
2
3function getCommonHeaders() {
4 return new Headers({
5 a: 1,
6 b: 2
7 })
8}
9function printHeaders(headers) {
10 const datas = headers.entries();
11 for (const pair of datas) {
12 console.log(`key: ${pair[0]},value: ${pair[1]}`);
13 }
14}
15
16function getRequestInfo() {
17 if (!req) {
18 const url = "http://101.132.72.36:5100/api/local";
19 const headers = getCommonHeaders();
20 headers.set("a", 3)
21 req = new Request(url, {
22 headers
23 });
24 printHeaders(headers);
25 }
26 return req.clone();
27}
28
29async function getProvinces() {
30 const resp = await fetch(getRequestInfo())
31 printHeaders(resp.headers);
32 const result = await getJSON(resp);
33 console.log(result)
34}
35
36async function getJSON(resp) {
37 const json = await resp.json();
38 return json;
39}
9-6 文件上传
流程:
- 客户端将文件数据发送给服务器
- 服务器保存上传的文件数据到服务器端
- 服务器响应给客户端一个文件访问地址
测试地址:http://101.132.72.36:5100/api/upload
键的名称(表单域名称):imagefile
请求方法:POST
请求的表单格式:multipart/form-data
请求体中必须包含一个键值对,键的名称是服务器要求的名称,值是文件数据
HTML5中,JS仍然无法随意的获取文件数据,但是可以获取到input元素中,被用户选中的文件数据
自己创建请求体很麻烦,所以可以利用HTML5提供的FormData构造函数来创建请求体
1<img src="" alt="" id="imgAvatar">
2<input type="file" id="avatar">
3<button>上传</button>
4<script>
5 async function upload() {
6 const inp = document.getElementById("avatar");
7 if (inp.files.length === 0) {
8 alert("请选择要上传的文件");
9 return;
10 }
11 const formData = new FormData(); //构建请求体
12 formData.append("imagefile", inp.files[0]);
13 const url = "http://101.132.72.36:5100/api/upload"
14 const resp = await fetch(url, {
15 method: "POST",
16 body: formData //自动修改请求头,自动完成了headers:{"Content-Type":"multipart/form-data"}
17 });
18 const result = await resp.json();
19 return result;
20 }
21
22 document.querySelector("button").onclick = async function () {
23 const result = await upload();
24 console.log(result)
25 const img = document.getElementById("imgAvatar")
26 img.src = result.path;
27 }
28</script>
文件保存的地址:

10.迭代器和生成器
本节整理迭代器和生成器相关知识点。
11.更多的集合类型
11-1. set集合
一直以来,JS只能使用数组和对象来保存多个数据,缺乏像其他语言那样拥有丰富的集合类型。因此,ES6新增了两种集合类型(set 和 map),用于在不同的场景中发挥作用。
set用于存放不重复的数据
- 如何创建set集合
1new Set(); //创建一个没有任何内容的set集合
2new Set(iterable); //创建一个具有初始内容的set集合,内容来自于可迭代对象每一次迭代的结果
演示
1const set1 = new Set();
2console.log(set1);
3const set2 = new Set([1, 2, 3, 3, 4, 4, 5, 6]);//简单的数组去重
4console.log(set2);
5const s2 = new Set("asdfasfasf");//会把字符串转成string对象,string对象可迭代
6console.log(s2);
- 如何对set集合进行后续操作
- add(数据): 添加一个数据到set集合末尾,如果数据已存在,则不进行任何操作
- set使用Object.is的方式判断两个数据是否相同,但是,针对+0和-0,set认为是相等
- Object.is(+0,-0)----false
- has(数据): 判断set中是否存在对应的数据
- delete(数据):删除匹配的数据,返回是否删除成功
- clear():清空整个set集合
- size: 获取set集合中的元素数量,只读属性,无法重新赋值
- 如何与数组进行相互转换
1const s = new Set([x,x,x,x,x]);
2// set本身也是一个可迭代对象,每次迭代的结果就是每一项的值
3const arr = [...s];
数组去重、字符串去重
1const arr = [45, 7, 2, 2, 34, 46, 6, 57, 8, 55, 6, 46];
2const result = [...new Set(arr)];
3console.log(result);
4const str = "asf23sdfgsdgfsafasdfasfasfasfsafsagfdsfg";
5const s = [...new Set(str)].join("");// 转成数组后转成字符串
6console.log(s);
现有字符串”aaabbbcccdddeefggaa”,转换成连续不重复的字符串例如:abcdefga。(用正则写满分,其他方法酌情给分)(10分)
1var reg = /(\w)\1*/g
2var str = "aaabbbcccdddeefggaa";
3console.log(str.replace(reg, '$1'))
- 如何遍历
1). 使用for-of循环
1const s1 = new Set();
2for (const item of s1) {
3 console.log(item)
4}
2). 使用set中的实例方法forEach
1s1.forEach((item, index, s) => {
2 console.log(item, index, s);
3})
4console.log(s1);
5console.log("总数为:", s1.size);
注意:set集合中不存在下标,因此forEach中的回调的第二个参数和第一个参数是一致的,均表示set中的每一项
11-2. set应用
1// 两个数组的并集、交集、差集 (不能出现重复项),得到的结果是一个新数组
2const arr1 = [33, 22, 55, 33, 11, 33, 5];
3const arr2 = [22, 55, 77, 88, 88, 99, 99];
并集
去掉各自里面重复的和他们合起来重复的
1// 写法1
2const s = new Set(arr1.concat(arr2));// 连起来在去重
3const result = [...s];
4console.log(result);
5// 写法2
6const result = [...new Set(arr1.concat(arr2))];
7console.log('并集', result);
8// 写法3
9console.log("并集", [...new Set([...arr1, ...arr2])]);
交集
1const s1 = new Set(arr1);
2const s2 = new Set(arr2);
3// 方法1
4// [...s1].filter(item => {
5// return s2.has(item);
6// })
7// 方法2
8// [...s1].filter(item => {
9// return arr2.indexOf(item) >= 0
10// })
11// 方法3
12// const cross = [...new Set(arr1)].filter(item => arr2.indexOf(item) >= 0);
13// console.log("交集", cross)
差集
1// console.log("差集", [...new Set([...arr1, ...arr2])].filter(item => arr1.indexOf(item) >= 0 && arr2.indexOf(item) < 0 || arr2.indexOf(item) >= 0 && arr1.indexOf(item) < 0))
2console.log("差集", [...new Set([...arr1, ...arr2])].filter(item => cross.indexOf(item) < 0))//交集里面没有的
11-4. map集合
键值对(key value pair)数据集合的特点:键不可重复
map集合专门用于存储多个键值对数据。
在map出现之前,我们使用的是对象的方式来存储键值对,键是属性名,值是属性值。
使用对象存储有以下问题:
- 键名只能是字符串(或符号)
- 获取数据的数量不方便
- 键名容易跟原型上的名称冲突
- 如何创建map
1new Map(); //创建一个空的map
2new Map(iterable); //创建一个具有初始内容的map,初始内容来自于可迭代对象每一次迭代的结果,但是,它要求每一次迭代的结果必须是一个长度为2的数组,数组第一项表示键,数组的第二项表示值
1const mp1 = new Map([["a", 3], ["b", 4], ["c", 5]]);
- 如何进行后续操作
- size:只读属性,获取当前map中键的数量
- set(键, 值):设置一个键值对,键和值可以是任何类型
- 如果键不存在,则添加一项
- 如果键已存在,则修改它的值,覆盖
- 比较键的方式和set相同
- get(键): 根据一个键得到对应的值
- has(键):判断某个键是否存在
- delete(键):删除指定的键
- clear(): 清空map
1const mp1 = new Map([["a", 3], ["b", 4], ["c", 5]]);
2console.log("总数:", mp1.size);
3console.log("get('a')", mp1.get("a"));
4console.log("has('a')", mp1.has("a"));
1这是两个,因为对象-地址
2mp1.set({}, 232);
3mp1.set({}, 111);
4要想是一个
5var obj = {}
6mp1.set(obj, 6456);
7mp1.set(obj, 111);
- 和数组互相转换
和set一样
1const mp = new Map([
2 ["a", 3],
3 ["c", 10],
4 ["b", 4],
5 ["c", 5]
6]);
7const result = [...mp]
8console.log(result);
- 遍历
- for-of,每次迭代得到的是一个长度为2的数组
- forEach,通过回调函数遍历
- 参数1:每一项的值
- 参数2:每一项的键
- 参数3:map本身
1// for (const [key, value] of mp) {
2// console.log(key, value)
3// }
4mp.forEach((value, key, mp) => {
5 console.log(value, key, mp)
6})
11-6. [扩展]WeakSet和WeakMap
WeakSet
1let obj = {
2 name:"yj",
3 age:18
4}
5const set = new Set();
6set.add(obj);
7obj = null;//请问此时set里面的对象还在吗??在。
8console.log(obj)
使用该集合,可以实现和set一样的功能,不同的是:
- 它内部存储的对象地址**不会影响垃圾回收**
1let obj = {
2 name: "yj",
3 age: 18
4};
5const set = new WeakSet();
6set.add(obj);
7obj = null;
8console.log(set);
- 只能添加对象
- 不能遍历(不是可迭代的对象)、没有size属性、没有forEach方法
1let obj = {
2 name: "yj",
3 age: 18
4};
5let obj2 = obj;
6const set = new WeakSet();
7set.add(obj);
8
9obj = null;
10//obj2 = null;
11console.log(set)
WeakMap
1<ul>
2 <!-- { id:"1", name:"姓名1" } -->
3 <li>1</li>
4 <!-- { id:"2", name:"姓名2" } -->
5 <li>2</li>
6 <!-- { id:"3", name:"姓名3" } -->
7 <li>3</li>
8</ul>
9<script>
10 const wmap = new WeakMap();
11 let lis = document.querySelectorAll("li");
12 for (const li of lis) {
13 wmap.set(li, {
14 id: li.innerHTML,
15 name: `姓名${li.innerHTML}`
16 });
17 }
18 lis[0].remove();
19 lis = null;
20 console.log(wmap);
21</script>
类似于map的集合,不同的是:
- 它的键存储的地址不会影响垃圾回收
- 它的键只能是对象
- 不能遍历(不是可迭代的对象)、没有size属性、没有forEach方法
12. 代理与反射
本节整理代理与反射相关知识点。
13. 增强的数组功能
13-1. 新增的数组API
静态方法
- Array.of(...args): 使用指定的数组项创建一个新数组
1const arr = Array.of(1,2,3,4,5);
2const arr = new Array(1,2,3,4,5)
3// 作用一样为什么要出新的API呢?主要为了解决只有一个参数的时候
- Array.from(arg): 通过给定的类数组 或 可迭代对象 创建一个新的数组。
之前类数组转换成数组:Array.prototype.slice.call(divs, 0)
1<div></div>
2<div></div>
3<div></div>
4<div></div>
5<div></div>
6<div></div>
7<div></div>
8<div></div>
9<div></div>
10<div></div>
11<script>
12 const divs = document.querySelectorAll("div")// 类数组,原形不是Array,不是Array构造出来的
13 const result = Array.from(divs)
14 console.log(result);
15</script>
实例方法
- find(callback): 用于查找满足条件的第一个元素
- findIndex(callback):用于查找满足条件的第一个元素的下标
1const arr = [
2 {
3 name: "a",
4 id: 1
5 },
6 {
7 name: "b",
8 id: 2
9 },
10 {
11 name: "c",
12 id: 3
13 },
14 {
15 name: "d",
16 id: 4
17 },
18 {
19 name: "e",
20 id: 5
21 },
22 {
23 name: "f",
24 id: 6
25 },
26 {
27 name: "g",
28 id: 7
29 }
30]
31
32//找到id为5的对象
33const result = arr.find(item => {
34 if(item.id === 5) return true;
35 else return false
36})
37简写
38const result = arr.find(item => item.id === 5)
39const resultIndex = arr.findIndex(item => item.id === 5)
40
41console.log(result, resultIndex);
- fill(data):用指定的数据填充满数组所有的内容
1// 创建了一个长度为100的数组,数组的每一项是"abc"
2const arr = new Array(100);
3arr.fill("abc");
- copyWithin(target, start?, end?): 在数组内部完成复制
1const arr = [1, 2, 3, 4, 5, 6];
2//从下标2开始,改变数组的数据,数据来自于下标0位置开始
3// arr.copyWithin(2); // [1, 2, 1, 2, 3, 4]
4// arr.copyWithin(2, 1); // [1, 2, 2, 3, 4, 5]第二个参数:从哪个位置复制数据
5// arr.copyWithin(2, 1, 3); // [1, 2, 2, 3, 5, 6]第三个参数在哪个位置复制停止
6console.log(arr)
- includes(data):判断数组中是否包含某个值,使用Object.is匹配
1const arr = [45, 21, 356, 66 , 6, NaN, 723, 54];
2
3console.log(arr.indexOf(66) >= 0)
4console.log(arr.includes(NaN));
13-2. [扩展]类型化数组
数字存储的前置知识
- 计算机必须使用固定的位数来存储数字,无论存储的数字是大是小,在内存中占用的空间是固定的。
- n位的无符号整数能表示的数字是2^n个,取值范围是:0 ~ 2^n - 1
- n位的有符号整数能表示的数字是2n个,取值范围是:-2(n-1) ~ 2^(n-1) - 1
- 浮点数表示法可以用于表示整数和小数,目前分为两种标准:
- 32位浮点数:又称为单精度浮点数,它用1位表示符号,8位表示阶码,23位表示尾数
- 64位浮点数:又称为双精度浮点数,它用1位表示符号,11位表示阶码,52位表示尾数
- JS中的所有数字,均使用双精度浮点数保存
类型化数组
类型化数组:用于优化多个数字的存储
具体分为:
- Int8Array: 8位有符号整数(-128 ~ 127)
- Uint8Array: 8位无符号整数(0 ~ 255)
- Int16Array: ...
- Uint16Array: ...
- Int32Array: ...
- Uint32Array: ...
- Float32Array:
- Float64Array
-
如何创建数组
1new 数组构造函数(长度)
2
3数组构造函数.of(元素...)
4
5数组构造函数.from(可迭代对象)
6
7new 数组构造函数(其他类型化数组)
-
得到长度
1数组.length //得到元素数量
2数组.byteLength //得到占用的字节数
- 其他的用法跟普通数组一致,但是:
- 不能增加和删除数据,类型化数组的长度固定
- 一些返回数组的方法,返回的数组是同类型化的新数组
1// const arr = new Int32Array(10);
2const arr = Uint8Array.of(12, 5, 6, 7);
3console.log(arr);
4// console.log(arr.length);
5// console.log(arr.byteLength);
1const arr1 = Int32Array.of(35111, 7, 3, 11);
2
3const arr2 = new Int8Array(arr1);
4
5console.log(arr1 === arr2);
6console.log(arr1, arr2);
1const arr = Int8Array.of(125, 7, 3, 11);
2const arr2 = arr.map(item => item * 2)
3console.log(arr2);
4
5// arr[1] = 100;
6// console.log(arr);
7// console.log(arr[1])
8// for (const item of arr) {
9// console.log(item)
10// }
11
12// arr[4] = 1000; //增加无效
13// delete arr[0]; //删除无效
14// console.log(arr)
13-3. [扩展]ArrayBuffer
ArrayBuffer
ArrayBuffer:一个对象,用于存储一块固定内存大小的数据。
可以通过属性byteLength得到字节数,可以通过方法slice得到新的ArrayBuffer
1//创建了一个用于存储10个字节的内存空间
2const bf = new ArrayBuffer(10);
3const bf2 = bf.slice(3, 5);//起始的字节数,结束的字节数,不填默认赋值旧数组
4console.log(bf, bf2);
读写ArrayBuffer
- 使用DataView
通常会在需要混用多种存储格式时使用DataView
- 使用类型化数组
实际上,每一个类型化数组都对应一个ArrayBuffer,如果没有手动指定ArrayBuffer,类型化数组创建时,会新建一个ArrayBuffer
1//创建了一个用于存储10个字节的内存空间
2const bf = new ArrayBuffer(10);
3const view = new DataView(bf, 3, 4);// 偏移量3,操作4
4// console.log(view);
5view.setInt16(1, 3);// 设置
6console.log(view);
7console.log(view.getInt16(1));// 读取数据
类型化数组
1const bf = new ArrayBuffer(10); //10个字节的内存
2const arr1 = new Int8Array(bf);
3const arr2 = new Int16Array(bf);
4console.log(arr1 === arr2);// 类型化数组不一样
5console.log(arr1.buffer === arr2.buffer);// 操作的内存空间一样
6// 操作
7arr1[0] = 10;
8console.log(arr1)
9console.log(arr2);
1const bf = new ArrayBuffer(10); //10个字节的内存
2const arr = new Int16Array(bf);
3arr[0] = 2344; //操作了两个字节
4console.log(arr);
13-4. [扩展]制作黑白图片
1async function test() {
2 const resp = await fetch("./img/liao.jpg");
3 // console.log(resp);
4 const blob = await resp.blob(); // 图片数据是二进制格式,文件信息,要用blob访问
5 // console.log(blob);
6 const bf = await blob.arrayBuffer();
7 // console.log(bf);
8 const arr = new Int8Array(bf, 3, 2); //可以设置处理
9 console.log(arr);
10 }
11 test();
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <meta charset="UTF-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6 <meta http-equiv="X-UA-Compatible" content="ie=edge" />
7 <title>Document</title>
8 </head>
9
10 <body>
11 <div style="display: flex">
12 <img src="./img/liao.jpg" alt="" />
13 <button onclick="change()">转换</button>
14 <canvas width="100" height="117"></canvas>
15 </div>
16
17 <script>
18 /*
19 * 画布中的1个图像是由多个像素点组成,每个像素点拥有4个数据:红、绿、蓝、alpha
20 * 把一个图像变成黑白,只需要将图像的每个像素点设置成为红绿蓝的平均数即可
21 */
22
23 function change() {
24 const img = document.querySelector("img");
25 const cvs = document.querySelector("canvas");
26 const ctx = cvs.getContext("2d");
27 ctx.drawImage(img, 0, 0);
28 //得到画布某一个区域的图像信息
29 const imageData = ctx.getImageData(0, 0, img.width, img.height);
30 console.log(imageData);
31 // // 150 100 50 平均值100-> 都设置成100
32 for (let i = 0; i < imageData.data.length; i += 4) {
33 // //循环一个像素点
34 const r = imageData.data[i];
35 const g = imageData.data[i + 1];
36 const b = imageData.data[i + 2];
37 const avg = (r + g + b) / 3; // 平均
38 imageData.data[i] =
39 imageData.data[i + 1] =
40 imageData.data[i + 2] =
41 avg;
42 }
43 // //将图像数据设置到画布
44 ctx.putImageData(imageData, 0, 0);
45 }
46 </script>
47 </body>
48</html>
ECMAScript++
- Math.pow —— ES2015
2. Object.entries Object.values —— ES2015
3. ES2019新增数组API
Array.prototype.flat
该函数可以将某个数组拍扁
Array.prototype.flatMap
示例1:利用flatMap在map期间去掉一些数据
1const arr = [1, 2, 3, 4, 5];
2
3/*
4 [
5 {number:1, doubleNumber: 2},
6 {number:3, doubleNumber: 6},
7 {number:5, doubleNumber: 10},
8 ]
9*/
写法1
1const arr = [1, 2, 3, 4, 5];
2var newArr = [];
3for(const item of arr){
4 if(item % 2 !== 0){
5 newArr.push({
6 number:item,
7 doubleNum:item*2
8 })
9 }
10}
11console.log(newArr)
写法2:先筛选后映射
1var result = arr
2.filter((it) => it % 2 === 1)
3.map((it) => ({number:it, doubleNumber:it*2}));
4console.log(result);
不建议的方法3
1var result = arr
2.map((it) => (it%2===1?{number:it,doubleNumber:it*2}:[]))
3.flat();
4console.log(result);
5//等价于
6var result = arr.flatMap((it) => it%2===1?{number:it,doubleNumber:it*2}:[])
7console.log(result);
示例2:利用flatMap分割一个单词数组
1const arr = [
2 "Yestoday is a History",
3 "Tomorrow is a Mystery",
4 "Today is a Gift",
5 "That's why we call it the Present"
6];
7/*
8 ["Yestoday", "is", "a", "History", "Tomorrow", ...]
9*/
写法1:
1const arr = [
2 "Yestoday is a History",
3 "Tomorrow is a Mystery",
4 "Today is a Gift",
5 "That's why we call it the Present"
6];
7var result = arr.map((it) => it.split(" "));
8//console.log(result);
9var result = arr.map((it) => it.split(" ")).flat();
10console.log(result)
11// 等价于
12var result = arr.flatMap((it) => it.split(""));
Object.fromEntries
1Object.fromEntries(iterable)
它接收一个可迭代对象,该对象每次迭代必须返回一个包含两项数据的数组(参考map),该函数会将第一项作为对象的属性名,第二项作为对象的属性值
1const arr = [["a", 1], ["b", 2]]
2Object.fromEntries(arr); // {a:1, b:2}
示例:
1function localMoneyFomat(obj){
2 //略
3}
4
5var obj = {
6 name:"xxx",
7 balance: 199.8, //余额
8 taken: 3000 //消费
9}
10localMoneyFomat(obj); // {name:"xxx", balance:"¥199.8", taken: "¥3000"}
答案
1function localMoneyFomat(obj){
2 var result = Object.entries(obj)// 变成数组
3 console.log(result)
4 var result = Object.entries(obj).map(it => typeof it[1] === "number"?[it[0], `¥${it[1]}`]:it)
5 console.log(result)
6}
7
8var obj = {
9 name:"xxx",
10 balance: 199.8, //余额
11 taken: 3000 //消费
12}
13localMoneyFomat(obj); // {name:"xxx", balance:"¥199.8", taken: "¥3000"}
解构的方法:
1function localMoneyFomat(obj){
2 var result = Object.entries(obj).map(([k, v]) => typeof v === 'number'?[k,`¥${v}`]:[k,v])
3 return Object.fromEntries(result)
4 console.log(result)
5}
String.prototype.trimStart
同trimLeft,去掉字符串左边的空格
1var str = " abc ";
2console.log(str.length);
3var str1 = str.trimStart();
4console.log(str1)
String.prototype.trimEnd
同trimRight,去掉字符串右边的空格
ES2020
以前的问题
1var person = {
2 info:{
3 addr:{
4 province:"黑龙江",
5 city:"哈尔滨",
6 },
7 },
8}
9// 如果这是对象是ajax远程获取的,不知道里面是不是空
10console.log(person.info.addr.city);
11// person可能是NUll,person.info可能是null,person.info.addr可能是null
以前的解决方案:
1console.log(person && person.info && person.addr && person.info.addr.city);
可选链操作符(Optional Chaining)
通过可选链
1console.log(person?.info?.addr?.city);
空位合并操作符(Nullish coalescing Operator)
1a ?? b
2// 等同于
3a === undefined || a === null ? b : a // a没有东西就取b,有东西就取a
应用
1function method(option) {
2 var a = option.a || 3;
3 console.log(a);
4}
5method({
6 a: 1,
7 // a:null/undefined/0
8 b: 2,
9 c: 3
10})
此方法有bug,当a:0
1function isDef(vaule) {
2 return value === undefined || value === null;
3}
4function method(option) {
5 var a = isDef(option.a) || 3;
6 console.log(a);
7}
优化
1function method(option) {
2 var a = option.a ?? 3;
3 console.log(a);
4}
5method({
6 a: 1,
7 // a:null/undefined/0
8 b: 2,
9 c: 3
10})
复习数据类型typeof返回:String Number undefined object Boolean function symbol
1// 第8种类型
2const a = Number.MAX_SAFE_INTEGER * Number.MAX_SAFE_INTEGER;
3// Number.MAX_SAFE_INTEGER最大安全整数
4// => 8.112963841460666e+31精读丢失了
5const b = BigInt(Number.MAX_SAFE_INTEGER) * BigInt(Number.MAX_SAFE_INTEGER);
6// => 81129638414606663681390495662081n
7typeof b // => bigint
8a + b // error: Cannot mix BigInt and other types
ES2021
- String.prototype.replaceAll
替换字符串中所有的匹配字符
上层工具:bit.dev研究一下
-
var、let、const之间的区别?
-
作用域
-
重复声明
-
是否会挂载到window
-
定义前访问(暂时性死区 TDZ)
-
语义
-
ES6中的class和传统的构造函数有什么区别?
-
是否必须使用new调用
-
严格模式
-
原型上的方法是否可被枚举
-
原型上的方法是否能使用new调用
1class A {
2 method1() {}
3 method2() {}//不可枚举
4}
5
6// new A.prototype.method1();不能new
7function B() {}
8B.prototype.m1 = function () {};//可枚举
9B.prototype.m2 = function () {};
10
11// console.log(Object.keys(B.prototype));
12new B.prototype.m1();//可以new
- 箭头函数和普通的函数表达式有什么区别?
1function m() {
2 var m2 = () => {
3 console.log(arguments.length);//箭头函数没有arguments 5
4 };
5
6 m2(1, 2, 3);
7}
8m(1, 2, 3, 4, 5);
下面Set结构,打印出的size值是多少
1let s = new Set();
2s.add([1]);
3s.add([1]);//字面量 这是两个数组
4console.log(s.size);