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

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