Hello JS

背景

1995 年 Netscape 公司的 Brendan Eich 创造了 Javascript。

1996 年 Netscape 公司将 JavaScript 提交给了标准化组织 ECMA。ECMA 发布了 262 号文件即 ECMA-262,将这种语言标准命名为 EMCAScript,由 ECMA 下属的 TC39 技术委员会编写。TC39 的成员包含了各主流浏览器厂商。

基本构成

JS = ES + DOM + BOM

运行环境

也可以叫使用场景

客户端

浏览器 渲染引擎(即浏览器内核) JS 引擎
Chrome Blink V8
Edge
Opera
QQ 浏览器等国内浏览器
Android 移动端浏览器
Safari Webkit JavaScriptCore
iOS 移动端浏览器
Firefox Gecko
IE Trident

Google 早期使用 Webkit 引擎,后从 Webkit 创建 Blink 分支。

不同 JS 引擎对 ES 标准实现程度不一样,V8 原是 C++开源项目,后被 Google 收购并开发。


Q:Chromium 属于哪一类,和 Chrome 是什么关系?

A:Chromium 不属于三类中任何一类,只是 Google 的一个开源的浏览器项目,Chrome 就是在 Chromium 的基础上封装了更多的功能,国内大多数浏览器都是基于 Chromium 开发的。


服务端

基于 V8 引擎的 NodeJS 环境,可进行服务端编程并提供后端服务能力。

ECMAScript

JS 的语法标准

各浏览器厂商基于 ECMAScript 标准,实现 JS 引擎即解释器对 JS 代码解释执行。


Q:JavaScript 与 ECMAScript 的关系?

A:ES 是标准,JS 是实现。


TC39 技术委员会每年 6 月发布一个 ES 版本,版本号以年份命名,例如 ES2015(即 ES6)

版本发布流程:提案(Proposal)->审批->标准(Standard),其中又细分为以下 5 个阶段。

阶段 含义 备注
stage0 Strawman-展示阶段
stage1 Proposal-征求意见阶段
stage2 Draft-草案阶段 进入 stage2 的提案最后基本都会成为标准
stage3 Candidate-候选阶段
stage4 Finish-结案阶段

大小写

区分大小写,包括关键字,例如 typeof 是关键字,但 typeOf 是可用变量名(虽然不建议用)。

分隔符

符号 含义 备注
; 分号 句末可以省略分号,但有些场景需要
' 单引号
" 双引号

Q: 哪些场景下需要分号

A:场景一: [].slice.apply()前面需要分号,因[]开头与前面语句难以断句;

场景二:IIFE 立即执行函数前面需要分号,(function(){}())(function(){})() 前面(上句末或本句前)需要有分号


模式

模式名称 指定方式 备注
普通模式 默认
严格模式 首行增加预处理指令'use strict'; ES5 引入的概念,语法要求更严格,编译时即报错
例如 var 变量必须先定义再使用否则报错

变量

定义

类型 作用域 声明方式 备注
var 函数作用域 var xxx = 'hello' 可重复声明
let 块级作用域 let xxx = 'hello' 不能重复声明
const 块级作用域 const xxx = 'hello 不能重复声明,声明时需要初始化,声明后不能修改
var

已不建议使用 var,作用域污染

如果在全局作用域下(即不在函数块中)设置 var,和通过 window.xxx=yyy 设置相同,可通过 window.xxx 获取,而 let/const 在全局下定义虽然不能通过 window 获取到,但实际仍是全局作用域。

作用域提升

不论在函数什么位置用 var 声明变量,编译时都会提升到函数最开头。即在 var 声明之前打印变量不会报错,会打印 undefined。而且可以重复声明。

let/const 不存在作用域提升,因此上述情况会报错。

let

块级作用域

块级作用域由最近的一对{}界定范围,函数作用域更小例如 for 的{}就形成了一个块级作用域,每次循环都是一个块级作用域。

ES6 之前最小直到函数作用域,循环绑定事件需要通过 IIFE 才能形成自己的块级作用域,不会被块外面修改变量而影响;ES6 之后循环如果使用块级作用域的变量,每一次循环都会创建独立的变量。

const

const 优先于 let 使用,便于编译时静测

声明后不能修改,对于基础类型变量不能修改值,而对于引用类型是不能修改引用地址,属性值可以增删改。

数据类型

基础类型

6 类,存储在栈中

类型 含义
undefined 未定义
null 空值
String 字符串
Number 数字
Boolean 布尔值
Symbol 符号
undefined

变量未声明或已声明未初始化时的类型。可以用void 0来表示 undefined,避免无意间修改 undefined。

null

Q:nullundefined 区别

A:undefined 其实是 window 下的全局变量,而 null 是关键字


null == undefined; // true

null === undefined; // false

typeof null; // 'object'

typeof undefined; // 'undefined'


String

// 定义

let str = "world";

属性

str.length


const str = "hello";

// 返回字符串长度,即字符个数

str.length; // 5

方法

str.toLowerCase()


const str = "Hello";

// 返回全部字符转换为小写的字符串

const newStr = str.toLowerCase(); // 'hello'

str.charAt()


const str = "hello";

// 返回指定索引位置的字符

const newStr = str.charAt(0); // 'h', 同样适用于中文字符串,会获取一个汉字

str.trim()


const str = "  hello  ";

// 返回去除收尾两端空白符的新字符串

const newStr = str.trim(); // 'hello'

str.indexOf(searchValue [, fromIndex])


const str = "hello";

// 返回搜索的子串所在索引位置, 未查找到返回-1

str.indexOf("el"); // 1

str.includes(searchString[, position])


const str = "hello";

// 返回是否在字符串中查找到字串

str.includes("el"); // true

str.replace(regexp|substr, newSubStr|function)str.replace


const str = "hello";

let newStr = "";

// 返回替换的新字符串

newStr = str.replace("e", "l"); // 'hlllo'

newStr = str.replace(/l/gi, "c"); // 'hccco'

str.replaceAll(regexp|substr, newSubstr|function)

替换所有,如果是正则表达式,则必须带修饰符 g

str.match(regex)


const str = "hello.doc";

// 匹配到返回数组,未匹配到返回null

const result1 = str.match(/\.docx?$/); // [.doc]

const result2 = str.match(/\.docx$/); // null

str.split([separator[, limit]])


const str = "hello world";

// 返回拆分后组成的数组

const arr = str.split(" "); // ['hello', 'world']

str.substring(indexStart[, indexEnd])


const str = "hello";

// 返回截取的字符串,indexEnd缺省默认截取到结尾

const newStr1 = str.substring(0); // 'hello'

// 返回的字符串不包含indexEnd位置的字符

const newStr2 = str.sbustring(1, 2); // 'e'

str.substr(start[, length])

已被废弃,应该改用 substring


const str = "hello";

// 返回截取的字符串

const newStr = str.substr(1, 2); // 'el'

str.startsWith(searchString[, position])


const str = "hello";

// 返回是否以'he'开头

console.log(str.startsWith("he")); // true

类型转换

// 其他类型转换成String

// 方式一,适用于非null和undefined的类型,因为null和undefined没有toString方法

xxx.toString();

// 方式二,适用于所有类型

String(xxx);

字符串拼接

let str1 = "world";

// 字符串拼接

// 方式一

let str2 = "hello" + str1;

// 方式二

let str3 = `hello${str1}`;

Number

let num = 0;

// 其他类型转换成Number,一般只针对本身字符串数字,否则转换后为NaN

// 方式一

Numer(xxx);

// 方式二

parseInt(xxx);

parserFloat(xxx) +

  // 方式三,针对字符串

  xxx;

取值范围

Number.MAX_VALUE; // 正值最大值1.7976931348623157e+308,计算结果大于最大值返回Infinity

Number.MIN_VALUE; // 正值最小值5e-324,即0.0000..5,计算结果小于最小值返回-Infinity

浮点数

// 因为精度问题,不能直接比较两个浮点数

0.1 + 0.2 == 0.3; // false,因为0.1+0.2将得到0.30000000000000004

// 浮点数比较方法,原理:相减小于最小精度则相等

Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON;

NaN

用于运算失败的返回结果,不是数字


1 / 0; // Infinity

0 / 0; // NaN

// 判断NaN只能通过isNaN,不能通过===

isNaN(xxx);

科学计数法

例如:3.12e5 或 3.12e+5,即 3.12*10 的 5 次方,312000

3.12e-5,即 3.12*10 的-5 次方,0.0000312

Boolean

// 其他类型转换成Boolean,针对字符串'true'或'false'转换成布尔值

// 方式一

Boolean("true"); // true

// 方式二

JSON.parse("true"); // true

Symbol

ES6 新增,可以理解为不会重复的值

Symbol()生成唯一记号,可用于确保对象的 key 唯一,实际使用场景不多。


// 局部定义

// 定义,Symbol()是函数而不是构造函数,不能使用new,区别于String/Boolean/Number

let sym1 = Symbol();

// 可以增加描述信息,xxx是描述信息,描述信息相当于注释说明,对值生成没有任何影响,每次返回值不相同

let sym2 = Symbol("xxx");

// 使用

let obj = {

  [sym1]: "cc",

  [sym2]: 18,

};

console.log(obj[sym1]);

// 全局定义

// 创建,全局唯一,具有幂等性,执行多次只会创建一次,每次返回相等

let sym = Symbol.for("xxx");

引用类型

存储在堆中,所有引用类型都继承自是 Object,typeof 引用类型都返回'object'

基础引用类型
类型 含义
Date 日期
RegExp 正则表达式
Math 数学运算
Global
包装类型 String
Boolean
Number
Date

提供关于日期和时间的信息,包括当前日期、时间及相关计算。


const date = new Date();

// 获取当前日期时间

const now = `${date.getFullYear()}-${

  date.getMonth() + 1

}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`; // 2020-10-29 16:34:25

// 获取当前时间戳毫秒值

const timestampNow = Date.now(); // 1652917948262

实例方法

date.getMonth()


const date = new Date();

// 返回当前月份的索引,取值范围[0-11],例如1月的索引是0

const monthIndex = date.getMonth(); // 4

date.getMinutes()


const date = new Date();

// 返回当前分钟的索引,取值范围[0-59]

const minuteIndex = date.getMinutes(); // 5,当前时间是x点过6分

RegExp

提供正则表达式能力。

定义


// pattern是正则表达式,attributes是正则模式符(例如i和g)

// 方式一

const regex1 = /pattern/attributes

// 方式二

csont regex2 = new RegExp(pattern, attributes)

元字符

元字符 含义 备注
. 匹配任意一个字符 英文句点
[] 匹配中括号内任意一个字符
\d 数字
\D 非数字
\w 数字、字母、下划线
\s 空白字符 小写 s
\S 非空白字符 大写 S
\b 单词边界
* 匹配 0 次到 n 次
+ 匹配 1 次到 n 次
? 匹配 0 次或 1 次
非贪婪模式修饰符
^ 匹配开头
中括号中取反 例如:匹配非数字[^\d]
& 匹配结尾
{} {n} 匹配 n 次
{n,} 匹配至少 n 次
{n,m} 匹配 n 到 m 次

Q:(a|b|c)和[abc]的区别

A:匹配结果一致,注意|只能用在小括号中


实例方法

regex.test(str)


// 返回是否字符串与正则匹配

const str = 'hello.doc'

/.+\.docx?$/.test(str)  // true

/.+\.xlsx?$/.test(str)  // false

贪婪模式/非贪婪模式


const str = "<h1>正则表达式</h1>";

// 贪婪模式

const strNew1 = str.replace(/<.*>/g, ""); // ''

// 非贪婪模式

const strNew2 = str.replace(/<.*?>/g, ""); // '正则表达式'

断言

只是正则的额外条件,不实际匹配字符

先行断言

浏览器普遍支持


const str = 'hello'

// ?=xxx  匹配紧跟xxx的

// 匹配紧跟o的l,会匹配的第二个l

/l(?=o)/.test(str)  // true

// ?!xxx  匹配不紧跟xxx的

// 匹配不紧跟o的l,会匹配的第一个l

/l(?!o)/.test(str)  // true

后行断言

ES9 才开始支持,浏览器普遍不支持,且没法通过 Babel 编译,因此 Babel 也会报错


const str = 'hello'

// ?<=xxx  匹配前面有xxx的

// 匹配前面有e的l,会匹配第一个l

/(?<=e)l/.test(str)  // true

// ?<!xxx  匹配前面没有xxx的

// 匹配前面没有e的l,会匹配第二个l

/(?<!e)l/.test(str)  // true

常用正则规则

匹配类型 正则 备注
匹配手机号 ^1\d{10}$
匹配汉字 ^[\u4e00-\u9fa5]*$ 空串或全部是汉字
[\u4e00-\u9fa5] 有汉字
匹配空行 ^\s*\n linux&mac 的换行符是\n
\s 是*而不是+,因为空白行也可能只有一个换行符没有\s
^\s*\r\n windows 的换行符是\r\n
Math

提供数学运算能力。

注意:Math 对象上提供的计算要比直接在 JavaScript 实现的快得多,因为 Math 对象上的计算使用了 JavaScript 引擎中更高效的实现和处理器指令。但使用 Math 计算的问题是精度会因浏览器、操作系统、指令集和硬件而异。

静态方法

Math.round(x)


// 四舍五入

Math.round(1.5); // 2

Math.ceil(x)


// 向上取整

Math.ceil(1.5); // 2

Math.floor(x)


// 向下取整

Math.floor(1.5); // 1

Math.random()


// 返回随机数(0到1之间的小数,含0不含1)

Math.random(); // 例如0.6108911334124585

Global

下图所列都是 Global 的属性,Global 无法直接访问,浏览器将 window 对象实现为 Global 对象的代理,即访问 window 相当于访问了 Global,当然 window 的作用远不止于此。

包装类型

// String/Number/Boolean对应的包装类型,声明方式不一样,例如:

// 基础类型的包装类型声明

let numObj = new Number(0);

// 基础类型声明

let num = 0;

numObj === num; // false

typeof num; // 'number'

typeof numObj; // 'object'

num.constructor === Number; // true

numObj.constructor === Number; // true

集合引用类型
Object

对象一组属性的无序集合,相当于一张散列表,属性由键名和键值组成,键名只能是字符串或 Symbo 类型,键值可以是任何数据类型或函数。


let obj = { name: "cc", age: 18 }; // name是key,cc是value

console.log(obj.name); // 'cc'


Q:对象的 key 重复会报错吗?

A:不会,如果 key 重名,只会保留相同 key 的最后一个键值对


隐藏属性

属性 含义 备注
configurable 是否可使用 delete 将该属性删除 一旦修改为 false,则无法再改成 true
且其他配置项无法再配置
enumerable 是否可以通过 for-in 循环
writable 值是否可修改
value key 对应的 value

const obj = {};

// 等价于Object.defineProperty(obj, 'name', {value: 'cc', writable: true, ...}),隐藏属性默认全是true

obj.name = "cc";

// 注意这样缺省配置隐藏属性默认全是false

Object.defineProperty(obj, "age", { value: 18 });

// 通过Object.defineProperty()进行修改,例如

Object.defineProperty(obj, "name", { value: "Sim", writable: false });

// 查看对象属性的隐藏属性

Object.getOwnPropertyDescriptors(obj); // {name: {value: 'cc', writable: false, ...}}

静态方法

Object.assign(target, ...sources)


const obj = { name: "cc", sex: "01" };

// 返回target,Object.assign(target, source1, source2, ...)

const newObj1 = Object.assign(obj, { name: "Sim", age: 18 }); // newObj1是obj的浅拷贝,obj本身也已改变, {name: 'Sim', sex:'01'}

const newObj2 = Object.assign({}, obj); // newObj2是obj的深拷贝,{name:'cc', sex: '01'}

Object.defineProperty(obj, prop, descriptor)


const obj = { name: "cc", sex: "01" };

// 设置对象的隐藏属性及setter和getter

Object.defineProperty(obj, "name", {

  value: "Sim",

  writable: false,

  set(value) {

    name = value;

    // 相当于添加一个钩子,在设置对象时触发,Vue响应式即基于此

    console.log(value);

  },

  get() {

    return name;

  },

});

Object.keys(obj)


const obj = { name: "cc", sex: "01" };

// 返回对象所有key组成的数组

const keys = Object.keys(obj); // ['name', 'sex']

解构

ES6 新增语法


const obj = { name: "cc", age: 18, sex: "01" };

const { name, age } = obj;

// 等价于:

// const name = obj.name

// const age = obj.name

console.log(name); // 'cc'

// 反之亦可

const name = "Sim";

const age = 20;

const obj2 = { name, age };

// 深层解构

const deepObj = { name: "cc", level: { badminton: 2, swimming: 1 } };

const {

  level: { badminton, swimming },

} = deepObj;

// 等价于:

// const badminton = deepObj.level.badminton

// const swimming = deepObj.level.swimming

console.log(badminton); // 2

Array

定义


// 声明

let arr1 = []; // 建议

let arr2 = new Array();

let arr3 = new Array(10);

// 声明同时初始化

let arr4 = [1, 2]; // 建议

let arr5 = new Array(1, 2);

// 预留特定索引位置

let arr6 = [1, , 3]; // 索引0的值是1,索引1的值是undefined,索引2的值是3

// 数组中元素可以是不同类型

let arr7 = [1, "hello", { name: "cc" }];

属性

arr.length


const arr = [1, 2, 3];

// 返回数组长度

console.log(arr.length); // 3

静态方法

Array.isArray(obj)


// 返回布尔值xxx是否为数组, ES6新增

Array.isArray(xxx); // 任意类型xxx都不会报错

Array.from(_arrayLike_[, _mapFn_[, _thisArg_]])


// 将类似数组的数据结构转换成数组,例如可以转换NodeList, ES6新增

Array.from(xxx);

实例方法

arr.toString()


const arr = [1, 2, 3];

// 返回逗号分隔的字符串

const str = arr.toString(); // '1,2,3'

arr.slice([begin[, end]])


const arr = [1, 2, 3]

// 返回包含索引0到1(不含)的数组,注意是浅拷贝

const newArr1 = arr.slice(0, 1) // [1]

// 默认返回从索引0到结尾的数组

const newArr2 = arr.slice() // [1,2,3]

// 返回索引倒数第2个到索引倒数第1个(不含)的数组

const newArr3 = arr.slice(-2, -1)  // [2]

// 索引前后关系错误返回空数组

const newArr4 = arr.slice(-1, -2) // []

// 特殊用法:类数组转换为数组,ES6可以直接使用Array.from(likeArr)

[].slice.apply(likeArr) // 将likeArr的各项添加到[]中, 同[].slice.call(likeArr)

arr.splice(start[, deleteCount[, item1[, item2[, ***...]]]]*)**


let arr = [];

// 数组中删除或新增项

// 从索引0开始删除1项

let arr = [1, 2, 3];

arr.splice(0, 1); // 原数组变成[2,3] 并返回[1]

// 在索引0处新增元素,删除0项

arr = [1, 2, 3];

arr.splice(0, 0, "cc"); // 原数组变成['cc',1,2,3] 返回[]

// 替换索引0处的元素

let arr = [1, 2, 3];

arr.splice(0, 1, 10); // 原数组变成[10,2,3] 返回[1]

// 在索引0处新增另外一个数组中的项,注意数组要展开

arr = [1, 2, 3];

let arr2 = [4, 5, 6];

arr.splice(0, 0, ...arr2); // 原数组变成[4,5,6,1,2,3]

// 默认参数从指定索引位置开始删除直到结尾

arr = [1, 2, 3];

arr.splice(1); // 原数组变成[1], 返回[2,3]

arr.indexOf(searchElement[, fromIndex])


const arr = [1, 2, 3];

// 在数组中查找元素(基础类型), 返回索引

const num = arr.indexOf(1); // 0

arr.findIndex(callback[, thisArg])


const arr = [{ name: "cc" }, { name: "Sim" }];

// 在数组中查找一个对象, 返回对象所在索引

const obj = { name: "cc" };

const num = arr.findIndex((item) => item.name == obj.name); // 0

arr.includes(valueToFind[, fromIndex])


const arr = [1, 2, 3];

// 返回是否找到某元素

const bool = arr.includes(1); // true

arr.shift()


let arr = [1, 2, 3];

// 弹出数组中第一个元素

arr.shift(); // 原数组变成 [2, 3],并返回1

arr.unshift(element1, ..., elementN)


let arr = [1, 2, 3];

// 在数组开头插入元素, 返回数组新长度

arr.unshift(-1, 0); // 原数组变成[-1, 0, 1, 2, 3], 并返回5

arr.push(element1, ..., elementN)


let arr = [1, 2, 3];

// 在数组尾部插入元素, 返回数组新长度

arr.push(4, 5); // 返回5, 原数组变成[1, 2, 3, 4, 5]

arr.pop()


let arr = [1, 2, 3];

// 弹出数组中最后一个元素

arr.pop(); // 原数组变成[1, 2],并返回3

arr.reverse()


let arr = [1, 2, 3];

// 逆序重排,原数组改变

arr.reverse(); // 原数组变成[3, 2, 1]

arr.reduce((previousValue, currentValue, currentIndex, array) => { /* ... */ }, initialValue)


const arr = [1, 2, 3];

// 对整个数组做累加或其他操作,initial指定时total使用initialValue,否则total使用第1个元素,空数组时不指定会报错

const num = arr.reduce((total, currentValue) => total + currentValue, 0); // 6

特殊用法


console.log(+[]); // 0

Set

ES6 新增

与数组相比,Set 不包含重复值(引用类型则是引用地址不能相同),且没有索引(但有序),因此不能像数组一样直接通过索引获取某个值,而只能通过迭代器按顺序获取。


// 创建

let s1 = new Set();

// 从数组创建

let arr = [1, 2, 3];

let s2 = new Set(arr);

// 从类数组NodeList创建

let divs = document.querySelectorAll("div");

let s3 = new Set(divs);

实例属性

set.size


let set = new Set();

// 返回Set的大小,即元素的个数

console.log(set.size); // 0

实例方法

set.add(value)


let set = new Set();

// 添加元素到末尾

set.add("cc");

set.has(value)


let set = new Set();

set.add("cc");

// 返回是否包含某元素

set.has("cc"); // true

set.delete(value)


let set = new Set();

set.add("cc");

// 删除元素,返回true表示有此元素被删除,false表示没有找到此元素

set.delete("cc"); // true

set.clear()


let set = new Set();

set.add("cc");

// 清空所有元素

set.clear();

set.values()

set.keys()

set.entries()


// 返回可迭代对象(用于for..of遍历),为了与Map类型保持一致,由于Set没有key只有value,把key也赋值成了value

set.values();

set.keys(); // 是values方法的别名

set.entries(); // key和value相同

特殊用法


// 查看html中总共有多少种标签

let tagCount = new Set(

  Array.from(document.querySelectorAll("*")).map((elem) => elem.nodeName)

);

WeakSet

只能是对象的集合,且集合中对象的引用为弱引用( 如果没有其他的对WeakSet中对象的引用,那么这些对象会被当成垃圾回收掉)

Map

key 可以是任意类型


let map = new Map();

实例属性

map.size


let map = new Map();

map.set("name", "cc");

// 返回map中键值对的数量

map.size; // 1

实例方法

map.set(key, value)

map.get(key)


let map = new Map();

// 设置键值对

map.set("name", "cc"); // key是字符串情况

let obj = { name: "Sim" };

map.set(obj, true); // key还可以是对象等任意类型

// 返回key对应value

map.get("name"); // 'cc'

map.get(obj); // true

注意 Map 类型不支持 JSON 序列化


let map = new Map();

map.set("name", "cc");

JSON.stringify(map); // 返回空{}


Q:Map 和 Object 的区别

A:如下表

Object Map
key 类型 String 或 Symbol 任意类型
元素个数 手动计算 map.size
赋值 obj.xxx = yyy map.set(xxx, yyy) Map 虽然能像 Object 一样赋值,但无法用自身实例方法获取到,因此必须用 set 方法赋值

WeakMap

相比 Map 类型,WeakMap 的 key 只能是对象类型,更重要的是 WeakMap 不会影响垃圾回收,即 WeakMap 中的对象如果在外部的所有使用到的地方都没有了,即可被垃圾回收掉,不会受 WeakMap 中使用了该对象影响,因此能够避免内存泄露。

运算符

! 逻辑反 返回布尔值,存在类型转换
例如 null/undefined/0/NaN/''/false 都返回为 true
!! 逻辑双重取反 返回布尔值,存在类型转换
例如 null/undefined/0/NaN/''/false 都返回为 false
void 将任何类型的表达式转换成 undefined
&& 逻辑与
|| 逻辑或
... 数组/对象等展开 返回数组/对象中所有元素
const arr1 = [1, 2, 3]
const arr2 = [...arr1]

条件

if


if(条件1){

    ...

}else if(条件2){

    ...

}else{

    ...

}

switch


switch(变量){

    case 字符串1:

        ...

        // 没有break会继续执行后面的代码

        break

    // 可针对不同的字符串使用相同的处理逻辑,前提是该case没有内容体

    case 字符串2:

    case 字符串3:

        ...

        break

    default:

        ...

        break

}

&&


// xx不为空时,a=xx

// xx为空时,a=yy

let a = xx || yy;

||


// xx不为空时,a=yy

// xx为空时,a=xx

let a = xx && yy;

&&||可以结合使用,简化条件语句

循环

for

xxx 可以是数组、对象、字符串


for (let i = 0; i < xxx.length; i++) {

  console.log(xxx[i]);

}

for-in

xxx 可以是数组、对象,xxx 是数组时 key 同时也是索引值


for (const key in xxx) {

  console.log(xxx[key]);

}

for-of

xxx 可以是数组、Set 等可迭代对象


for (const item of xxx) {

  console.log(item);

}

// 获取索引值的写法

for (const [i, item] of xxx.entries()) {

  console.log(item);

}

退出循环

关键字 含义 备注
continue 退出本次循环,继续循环往后
break 退出本层循环 注意由于 switch 中也有 break,如果 for 中嵌套 switch,switch 中的 break 不会触发退出本层循环
return 退出多层循环

以下为数组的函数式编程遍历方式:

forEach


arr.forEach((item, i) => {

  console.log(item);

});

every

遍历数组检查是否所有元素都满足指定条件,满足返回 true,否则返回 false


const result = arr.every((item) => item.id === id);

filter

遍历数组,返回符合条件数组元素组成新数组


const newArr = arr.filter((item) => item.id === id);

findIndex

遍历数组,返回第一个符合条件的数组元素索引,常用于对象数组中查找符合条件的对象


const index = arr.findIndex((item) => item.id === id);

map

遍历数组,常用于返回对象数组中对象的指定属性组成新数组


const newArr = arr.map((item) => item.id);


Q:循环遍历时操作数组对循环是否有影响

A:对数组的索引和值的对应关系有影响,因为删除改变了数组元素顺序,需要注意此问题可能导致循环不充分或不符合预期,注意规避


函数

函数都是 Function 类型的实例。


(function () {}.constructor === Function); // true

typeof function () {}; // 'function'

声明

5 种声明方式

普通函数

function hello() {

  console.log("Hello World");

}

匿名函数
字面量

将匿名函数赋值给变量


const hello = function () {

  console.log("Hello World");

};

箭头函数

普通匿名函数的简化写法


const hello = () => {

  console.log("Hello World");

};

参数

值传递

// str1和obj1是实参(实际参数),str和obj是形参(形式参数)

//

function hello(str, obj) {

  // 基础类型传递的是值

  str += " world"; // 参数str变成了hello world', str1还是'hello'

  // 引用类型传递的是引用地址, 即形参和实参栈地址不同,但指向同一堆地址

  obj.name = "Sim"; // obj和obj1的name都变成了Sim

  obj = {}; // 形参obj变成了{},而实参obj1还是{name:'Sim'}

}

let str1 = "hello";

let obj1 = { name: "cc" };

hello(str1, obj1);

默认值

可以直接在参数上赋默认值。


function hello(str1, str2 = "world") {

  console.log(str1 + str2);

}

// 可缺省传值,使用默认值

hello("hello");

实例方法

func.apply(thisArg, [argsArray])

function sum(num1, num2) {

  return num1 + num2;

}

function callSum(num1, num2) {

  // 调用sum函数,所有参数放到一个数组中

  return sum.apply(this, [num1, num2]);

}

console.log(callSum(1, 2)); // 3

func.call(thisArg, arg1, arg2, ...)

function sum(num1, num2) {

  return num1 + num2;

}

function callSum(num1, num2) {

  // 调用sum函数,所有参数单个传递

  return sum.call(this, num1, num2);

}

console.log(callSum(1, 2)); // 3


Q:apply 和 call 与直接调用函数有什么区别?

A:使用 call()或 apply()的好处是可以将任意对象设置为任意函数的作用域,这样对象可以不用关心方法。


window.color = "red";

let o = { color: "blue" };

function sayColor() {

  console.log(this.color);

}

sayColor(); // red

sayColor.call(this); // red

sayColor.call(window); // red

sayColor.call(o); // blue


func.bind(thisArg[, arg1[, arg2[, ...]]])

解决 this 指向问题


function func1(a, b, c) {

  return a + b + c;

}

// 基于原函数生成新函数,原函数的拷贝

// 此例只是演示,没有改变this指向

const func2 = func1.bind(this);

console.log(func2(10, 20, 30)); // 60

// 但注意与func2=func1相比,此方法相当于深拷贝

console.log(func2 == func1); // false

// 可配置参数

const func3 = func1.bind(this, 10);

console.log(func3(20, 30)); // 60

立即执行

IIFE,Immediately Invoke Function Expression

作用

声明和执行一条语句执行,作用域完全隔离。

写法

// 方式一

(function () {

  console.log("hello");

})()(

  // 方式二

  (function () {

    console.log("hello");

  })()

);

闭包

一个能访问外部函数作用域内变量的函数

作用

let 出现之前,常用的一种保护私有变量的机制,在函数执行时形成私有的作用域,保护里面的私有变量不受外界干扰。相当于 Java 中通过 getter 和 setter 操作私有变量。


<ul>

  <li>1</li>

  <li>2</li>

  <li>3</li>

</ul>


var btns = document.getElementsByTagName("button");

for (var i = 0; i < btns.length; i++) {

  // 结合函数立即执行传递参数

  (function (i) {

    btns[i].addEventListener(

      "click",

      // 闭包形成私有作用域,让i成为私有变量不受外界干扰

      function () {

        console.log(">>>This is ", i);

      }

    );

  })(i);

}

场景
  • 防抖(或节流)中的应用

通过闭包实现不需要全局变量也可以记录状态


const debounce = function (fn, mills) {

let context = null;

  // 返回函数赋值给onclick, 即此时onclick的回调函数已经是返回的函数而不是debounce

  return function () {

    if (!context) {

      fn();

    } else {

      clearTimeout(context);

    }

    context = setTimeout(() => {

      context = null;

    }, mills);

  };

}

btn.onclick = debounce(() => {

  // ...

}, 3000);

异步执行

回调函数

// 1000毫秒后执行function,默认0毫秒,但实际含义是0毫秒后放入任务队列待执行

const hello = setTimeout(function () {

  console.log("hello");

}, 1000);

Promise

ES6 新增,可以简化异步接口的有序执行,解决嵌套回调层次太深引发的「回调地狱」问题

Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值,是对尚不存在结果的一个替身。

一个 Promise 必然处于以下几种状态之一:

  • 待定(pending): 初始状态,既没有被兑现,也没有被拒绝。

  • 已兑现(fulfilled): 意味着操作成功完成。

  • 已拒绝(rejected): 意味着操作失败。

创建

const helloPromise = new Promise((resolve, reject) => {

  // resolve前的代码是同步代码

  // ...

  //  resolve(someValue)  // fulfilled

  // 或

  //  reject("failure reason")  // rejected

});


Q:Promise 和 Axios 的关系

A:Axios是 HTTP 请求工具,Promise 是异步执行机制,HTTP 请求都是异步请求,所以会用到 Promise


async await

简化 Promise 异步写法,用同步的写法写异步代码


async function hello(){

// await语句后面的语句,会等待await执行完才执行,相当于then,但简化了写法

// 1. await只能出现在async函数中

// 2. 只能接promise对象(async函数或者new Promise)

await hello2()

await hello3()

}

async hello2(){

console.log('hello2')

}

hello3(){

return new Promise(..)

}

面向对象

ES6 新增的class关键字只是一个语法糖,用来定义类,实际还是基于原型。例如:


class Point {

  constructor(x, y) {

    this.x = x

    this.y = y

  }

  toString() {

    return `(${this.x}, ${this.y})`

  }

}

// 等价于

Point.prototype = {

  constructor() { //... },

  toString() { //... }

}

构造函数

关键字是constructor ,在 class 定义中非必需,默认为空。解释器在处理 new 操作符创建类的新实例时,会调用 constructor 关键字修饰的函数。

constructor 中的this 指向新对象,成员变量可以不需要单独声明,直接在 constructor 中通过 this.xx 定义和初始化。当然也可以跟 Java 一样直接在 class 中先定义。

属性

类中声明属性不需要 let/const/function 修饰

属性分为状态行为

  • 状态

    • 实例变量

    • 静态变量

    • getter/setter

  • 行为

    • 实例方法

    • 静态方法

实例变量

一般无需直接定义,在constructor 中直接通过this.xxx 定义即可。

静态变量

在 class 中使用 static 声明静态变量当前尚未正式纳入 ES 标准中

在 class 外部定义,例如Point.info='hello'

实例方法

class Point {

  // ...

  hello() {

    console.log("hello");

  }

  // ...

}

const point = Point();

point.hello(); // 'hello'

静态方法

class Point {

  // ...

  static helloStatic() {

    console.log("hello static");

  }

  // ...

}

Point.helloStatic(); // 'hello static'

对象

每个实例化的对象会继承原型的属性和方法

对象的载体即 Object 类型。

迭代器

不需要再以索引为基础来构建循环集合,例如for-of的应用

其他

with

让作用域变的复杂,不建议使用,可用解构代替


with (location) {

  // search、hostname、href都是window.location下的变量,with可以简化写法

  console.log(search);

  console.log(hostname);

  console.log(href);

}

DOM

文档对象模型

HTML 编程接口,即 JS 与 HTML 交互的接口。

BOM

浏览器对象模型。

浏览器编程接口,即 JS 与浏览器交互的接口。例如 JS 中通过 window.location 获取浏览器页面访问的地址并进行操作。BOM 的顶级对象是 window 对象,JS 通过 window 对象对 DOM 和浏览器进行编程。


Q:BOM 和 DOM 的关系?

A:window.document 即 DOM 对象,可以说 BOM 包含了 DOM,但 DOM 标准是 W3C 制定的,BOM 的 document 是浏览器的实现。相当于浏览器在实现 DOM 标准的同时增加了对浏览器自身的编程接口,即 BOM=DOM+浏览器编程接口。


window:浏览器对象

NodeJS 的 global 对象即相当于 BOM 的 window。

全局对象

所有全局对象都属于 window,以下两种方式定义全局变量


// 方式一:不通过var/let/const声明的变量都是window下的全局变量

hello1 = "js";

console.log(window.hello1); // 'js'

// 方式二:全局下使用var声明的变量是window下的全局变量。

var hello2 = "js";

console.log(window.hello2); // 'js'

窗口操作

window.parent

父窗口对象,适用于在 iframe 中使用,如果没有父窗口,则等同于 window。

window.opener

当前窗口的被打开窗口对象,适用于在被 window.open()打开的窗口中使用。

window.scrollTo(x-coord, y-coord)


// 窗口滚动到指定坐标,例如顶部

window.scrollTo(0, 0);

window.scrollTo(options)

options

top 上边距

left 左边距

behavior

'auto': 默认,直接跳转到指定位置

'smooth': 平滑滚动到指定位置


// 窗口滚动到指定位置,例如平滑滚动到顶部

window.scrollTo({ top: 0, left: 0, behavior: "smooth" });

window.open(url, target, windowFeatures)

url

target

'_balnk': 默认,浏览器新标签打开

注意: windowFeatures 非空才会在浏览器新窗口打开

windowFeatures

width 窗口宽度

height 窗口高度

注意:宽高需要同时设置才生效


// 新标签打开百度

window.open("https://baidu.com");

// 新窗口(500px宽高)打开百度

window.open("https://baidu.com", "_blank", "width=500, height=500");

场景
网站上微信登录

微信扫码登录只能扫描微信官方页面上的二维码,没办法直接拿到二维码并展示

需求

  1. 登录页点击微信登录按钮,弹出新窗口打开微信扫码登录页面(微信官方页面)

  2. 扫码登录后跳转首页

  3. 自动关闭弹窗,并刷新页面 A 显示已登录状态

实现

登录页:基于 window.open 打开微信登录窗口


const url = "xxx"; // 微信扫码登录地址

window.open(url, "_blank", "width=500, height=500, top=200, left=500");

首页:弹窗中微信登录成功后跳转首页(url 参数中增加from=weixin),在首页里写关闭弹窗和重新加载登录页的逻辑

注意:需要根据是否存在 window.opener 即是否来自弹窗来触发事件,否则会影响首页正常访问逻辑


import Qs from "qs";

const query = Qs.parse(window.location.search.substring(1));

// 是否来自微信登录后跳转

const isFromWeixin = query.from === "weixin";

// 来自微信登录跳转且来自弹窗

if (isFromWeixin && window.opener) {

  // 关闭弹窗

  window.close();

  // 刷新打开弹窗的页面(即登录页),基于cookie或localstorage获取已登录状态

  window.opener.location.reload();

}

window.close()


// 关闭当前窗口(浏览器标签页)

window.close();

URL 操作

window.location

属性
window.location.href

// 页面跳转到指定URL

window.location.href = "https://baidu.com";

// 获取当前URL

console.log(window.location.href); // 'https://baidu.com'

包含关系

  • href
    • orgin
      • protocol
      • host
        • hostname
        • port
    • pathname
    • search
    • hash
方法
window.location.replace(url)

替换当前页面。返回上一页不会返回跳转前的页面,因为 history 中已经把上一个页面在队列中替换了。


window.location.replace("https://baidu.com");

window.location.reload()

当前页面重载。相当于浏览器 F5。


window.location.reload();

window.location.assign(url)

跳转,效果同给 href 赋值。history 中会把跳转的作为最后一项,跳转前后面的 history 将都被清除掉。


window.location.assign("https://baidu.com");

window.history

针对浏览器单个标签页而言,用户访问页面的队列,访问新页面才+1,且返回不会-1

方法
window.history.go()

页面前进后退,不会刷新页面。


// 当前页重载,两种方式

window.history.go();

window.history.go(0);

// 后退一页

window.history.go(-1);

// 前进一页

window.history.go(1);

window.history.back()

后退一页,同 window.history.go(-1),也同


window.history.back();

window.history.forward()

前进一页,同 window.history.go(1)


window.history.forward();

window.history.pushState()
window.history.replaceState()

其他操作

window.navigator.userAgent


console.log(window.navigator.userAgent); // 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36'

window.btoa(stringToEncode)

window.atob(encodedData)


// base64编码

const encodedData = window.btoa("hello");

// base64解码

const decodedData = window.atob(encodedData);

window.encodeURI(str)

转换为%开头的十六进制用于作为 URI 进行传输,主要用于转换汉字用于 URI 上传输

不会对字母 数字 ~!@#$&*()=:/,;?+'编码

实际上 POST 采用 x-www-form-urlencoded 就已经进行了编码,GET 的 URL 传参当前主流浏览器也会自动编码,不需手动处理


const str1 = ";,/?:@&=+$"; // 保留字符

const str2 = "-_.!~*'()"; // 不转义字符

const str3 = "#"; // 数字标志

const str4 = "ABC abc 123"; // 字母数字字符和空格

console.log(encodeURI(str1)); // ;,/?:@&=+$

console.log(encodeURI(str2)); // -_.!~*'()

console.log(encodeURI(str3)); // #

console.log(encodeURI(str4)); // ABC%20abc%20123

console.log(encodeURIComponent(str1)); // %3B%2C%2F%3F%3A%40%26%3D%2B%24

console.log(encodeURIComponent(str2)); // -_.!~*'()

console.log(encodeURIComponent(str3)); // %23

console.log(encodeURIComponent(str4)); // ABC%20abc%20123

window.encodeURIComponent(str)

不会对字母 数字 ~!*()'编码,即相对于 encodeURI,encodeURIComponent 会转义/ # ?这三个 URL 中会出现的字符

window.eval(string)


// 执行字符串JS语句

eval("console.log('hello')"); // 'hello'

window.postMessage(message, targetOrigin, [transfer])

窗口间通信。一个窗口可以获得对另一个窗口的引用(比如 targetWindow = window.opener),然后在窗口上调用 targetWindow.postMessage() 方法分发一个 MessageEvent 消息。


// 场景一:iframe和父窗口(window.parent)

// 1. 在iframe中发起向parent通信

const targetWindow = window.parent;

// message是要传递的参数对象

const message = { name: "cc", age: 18 };

// targetOrigin是要消息接收方所在域,如果不设置跨域情况会通信失败

const targetOrigin = "*"; // 注意:设置成*是存在安全隐患的,任何origin都能监听到此message

// 向targetOrigin发送消息

targetWindow.postMessage(message, targetOrigin);

// 2. parent中监听,注意只能在window下监听,不能指定的元素

// 为避免此事件被注册多次导致重复操作,此处建议使用DOM0事件模型写法绑定事件

window.onmessage = function (e) {

  // 开发调试时避免vue-devtools影响

  if (e.data.source && e.data.source.startsWith("vue-devtools")) {

    return;

  }

  console.log(e.data); // 传递过来的数据,即{name: 'cc', age: 18}

  console.log(e.origin); // 发送方窗口(此场景下即iframe)的 origin(域名、协议和端口),例如http://xxx.com,可用于校验身份

  console.log(e.source); // 发送方窗口对象的引用(此场景下即iframe),可以使用此来在两个窗口之间建立双向通信

};

// 场景二:新窗口和父窗口(window.opener),例如微信登录窗口与网站的通信

提示: 有些场景下 iframe 页面中的内容可能也是一个独立访问的页面,不需要传递消息否则会报错,就要增加触发条件,例如通过 URL 传参来标识页面访问的来源if(this.queryString.appFrom == 'school-workflow'){targetWindow.postMessage(...)}

注意:用于接收消息的任何事件监听器必须首先使用 origin 和 source 属性来检查消息的发送者的身份,因为不检查 origin 和 source 属性会可能导致跨站点脚本攻击。

原型

class 的 prototype 和 class 实例的__proto__的关系

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容