JavaScript基础
1 介绍
2 引入方式
js代码也是书写在html中的,那么html需要引入JS代码
方式有2种
第一种方式: 内部脚本,将JS代码定义在HTML页面中
- JavaScript代码必须位于
<script></script>
标签之间 - 在HTML文档中,可以在任意地方,放置任意数量的
<script>
- 一般会把脚本置于
<body>
元素的底部,可改善显示速度
例子:
<script>
alert("Hello JavaScript")
</script>
第二种方式: 外部脚本将, JS代码定义在外部 JS文件中,然后引入到 HTML页面中
- 外部JS文件中,只包含JS代码,不包含
<script>
标签 - 引入外部js的
<script>
标签,必须是双标签
例子:
<script src="js/demo.js"></script>
注意:demo.js中只有js代码,没有<script>
标签
3 语法-书写语法
掌握了js的引入方式,那么接下来我们需要学习js的书写了,首先需要掌握的是js的书写语法用Java编写规范即可
语法规则如下:
- 区分大小写:与 Java 一样,变量名、函数名Java中的方法名以及其他一切东西都是区分大小写的
- 每行结尾的分号
;
可有可无 Java中有,建议写 {}
大括号表示代码块- 注释:和Java一样
- 单行注释:
//
注释内容 - 多行注释:
/\* 注释内容 \*/
- 单行注释:
借助js中3钟输出语句,来演示书写语法
api | 描述 |
---|---|
window.alert() | 警告框 |
document.write() | 在HTML输出内容 |
console.log() | 写入浏览器控制台 |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JS-基本语法</title>
</head>
<body>
</body>
<script>
/* alert("JS"); */
//方式一: 弹出警告框
// window.alert("hello js");
// //方式二: 写入html页面中
// document.write("hello js");
//方式三: 控制台输出
console.log("hello js");
</script>
</html>
4 语法-变量 🍐
学习js中变量的声明
- 在js中,变量的声明和java中还是不同的
- js中主要通过如下3个关键字来声明变量的:
关键字 | 解释 |
---|---|
var | 早期ECMAScript5中用于变量声明的关键字 |
let | ECMAScript6中新增的用于变量声明的关键字,相比较var,let只在代码块内生效 |
const | 声明常量的,常量一旦声明,不能修改 |
在js中声明变量还需要注意如下几点:
- JavaScript 是一门弱类型语言,变量可以存放不同类型的值 。
- 变量名需要遵循如下规则按照Java规范即可:
- 组成字符可以是任何字母、数字、下划线(
_
)或美元符号($
) - 数字不能开头
- 建议使用驼峰命名
- 组成字符可以是任何字母、数字、下划线(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JS-基础语法</title>
</head>
<body>
</body>
<script>
//var定义变量
// var a = 10;
// a = "张三";
// alert(a);
//特点1 : 作用域比较大, 全局变量
//特点2 : 可以重复定义的
// {
// var x = 1;
// var x = "A";
// }
// alert(x);
//let : 局部变量 ; 不能重复定义
// {
// let x = 1;
// alert(x);
// }
//const: 常量 , 不能给改变的.
const pi = 3.14;
pi = 3.15;
alert(pi);
</script>
</html>
注意事项
- 在js中,我们var声明的变量可以接受任何数据类型的值。并且var声明的变量的作用于是全局的,注释掉之前的代码,
//var定义变量
var a = 10;
a = "张三";
a=true;
alert(a);
在ECMAScript 6 新增了 let 关键字来定义变量,它的用法类似于 var,但是所声明的变量,只在 let关键字所在的代码块内有效,且不允许重复声明,否则会出现错误
在ECMAScript6中简称:ES6,还新增了const 关键字用来声明常量,但是一旦声明,常量的值是无法更改的。注释之前的内容,否则会出现错误:
5 语法-数据类型和运算符 🍐
数据类型
虽然js是弱数据类型的语言,但是js中也存在数据类型,js中的数据类型分为 :原始类型 和 引用类型,具体有如下类型
数据类型 | 描述 |
---|---|
number | 数字(整数、小数、NaN(Not a Number)) |
string | 字符串,单双引皆可 |
boolean | 布尔。true,false |
null | 对象为空 |
undefined | 当声明的变量未初始化时,该变量的默认值是 undefined |
使用typeof函数可以返回变量的数据类型,接下来我们需要通过书写代码来演示js中的数据类型
//原始数据类型
alert(typeof 3); //number
alert(typeof 3.14); //number
点击查看验证代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS-数据类型</title>
</head>
<body>
</body>
<script>
//数字类型
alert(typeof 3); //number
alert(typeof 3.14); //number
// 字符串类型
alert(typeof "A"); //string
alert(typeof 'Hello');//string
//布尔类型
alert(typeof true); //boolean
alert(typeof false);//boolean
//对象
alert(typeof null); //object
//undefined未定义
var a ;
alert(typeof a); //undefined
</script>
</html>
运算符
js中的运算规则绝大多数还是和java中一致的和Java一样,具体运算符如下:
运算规则 | 运算符 |
---|---|
算术运算符 | + , - , \* , / , % , ++ , -- |
赋值运算符 | = , += , -= , *= , /= , %= |
比较运算符 | > , < , >= , <= , != , == , === 注意 == 会进行类型转换,=== 不会进行类型转换** 和java 不一样 |
逻辑运算符 | && |
三元运算符 | 条件表达式 ? true\_value: false\_value |
注意
- 在js中,绝大多数的运算规则和java中是保持一致的三元运算符也有,但是js中的
==和===
是有区别的。
==
:只比较值是否相等,不区分数据类型,哪怕类型不一致,==
也会自动转换类型进行值得比较===
:不光比较值,还要比较类型,如果类型不一致,直接返回false
var age = 20;
var _age = "20";
alert(age == _age);//true ,只比较值
alert(age === _age);//false ,类型不一样
- 流程控制语句 if,switch,for等和java保持一致,此处不再演示
在js中,虽然不区分数据类型,但是有时候涉及到数值计算,还是需要进行类型转换的,js中可以通过parseInt()函数来进行将其他类型转换成数值类型。注释之前的代码,添加代码如下:
// 类型转换 - 其他类型转为数字
alert(parseInt("12")); //12
alert(parseInt("12A45")); //12
alert(parseInt("A45"));//NaN (not a number)
- 需要注意的是: 在js中,0,null,undefined,"",NaN理解成false,反之理解成true
var a=0;
//a =null; a="" a=undefined
if(a){
//不会执行
}
6 语法-函数 ✏️ 🍐
函数
- java中我们为了提高代码的复用性,可以使用方法
- 在JavaScript中可以使用函数来完成相同的事情
- JavaScript中的函数被设计为执行特定任务的代码块 (封装),通过关键字function 来定义
- JS中定义函数有2中方式
第一种定义格式如下常用:
function 函数名(参数1,参数2..){
要执行的代码
}
因为JavaScript是弱数据类型的语言,所以有如下几点需要注意: 👈
- 形式参数不需要声明类型,并且JavaScript中不管什么类型都是let或者var去声明,加上也没有意义。
- 返回值也不需要声明类型,直接return即可
如下示例:
function add(a, b){
return a + b;
}
接下来我们需要在Idea或者VS中编写代码来演示
第一步:新建名为js的文件夹,创建名为01. JS-函数的html文件,然后在<script>
中定义上述示例的函数:
<script>
function add(a,b){
return a + b;
}
</script>
但是上述只是定义函数,**函数需要被调用才能执行!**所以接下来我们需要调用函数
第二步:因为定义的add函数有返回值,所以我们可以接受返回值,并且输出到浏览器上,添加如下代码:
let result = add(10,20);
alert(result);
第二种可以通过var去定义函数的名字,具体格式如下:
var functionName = function (参数1,参数2..){
//要执行的代码
}
接下来我们按照上述的格式,修改代码如下:只需要将第一种定义方式注释掉,替换成第二种定义方式即可,函数的调用不变
<script>
//定义函数-1
// function add(a,b){
// return a + b;
// }
//定义函数-2
var add = function(a,b){
return a + b;
}
//函数调用
var result = add(10,20);
alert(result);
</script>
注意
我们在调用add函数时,再添加2个参数,修改代码如下:
var result = add(10,20,30,40);
浏览器打开,发现没有错误,并且依然弹出30,这是为什么呢?
因为在JavaScript中,函数的调用只需要名称正确即可,参数列表不管的。如上述案例,10传递给了变量a,20传递给了变量b,而30和40没有变量接受,但是不影响函数的正常调用。
数据类型(8种) Type
JavaScript的8种原始数据类型包括Number、String、Boolean、Null、Undefined、Symbol(ES6新增)、BigInt(暂时不必关注),此外有Object类型。函数被认为是可执行的object类型变量,但typeof会返回'function'。
Number
不区分整数和浮点(内部区分),注意浮点计算结果是不精确的(由于浮点数的计算方式,如0.1+0.2=0.30000000000000004
),对浮点数不建议直接用等号判断两值相等,应当看他们的差是否小于某个值(如0.001)。Infinity(超过Number最大值)、NaN(Not a Number)也是合法的Number。
关于NaN
NaN是特殊的Number,它也不等于自己,只能通过isNaN()判断。
String
单引号‘’或双引号""包裹的字符串。
Boolean
Boolean包括'true'、'false'两种取值。比较运算符、&&、||与!都可能产生布尔值。
转换结果为false的取值
NaN、null、undefined、0与空字符串转Boolean的结果都为false。
Null
null表示空值,undefined表示未赋值,undefined可以用于判断函数参数是否传递。他们是两种数据类型。
Undefined
访问对象未声明的属性、函数未接受到的参数、未初始化的变量都会返回undefined。
null和undefined的注意事项
null是保留字,而undefined不是。
如果访问不存在的变量,会报错"var is not defined";访问已声明但未赋值的变量才会得到undefined。
typeof null的结果是object,但null也是一种基本类型而非object,这个混淆是由于typeof以内存低位判断数据类型,object和null的低3位都是0。
ymbol ES6
用于产生唯一标识,除了自己等于自己,两个完全相同的symbol不相等,常用于对象属性、声明唯一常量、定义私有属性。也可以用Symbol.for()创建symbol,如果参数一致,创建的symbol相等。
let s = Symbol("")
let s1 = Symbol("")
s == s1//false
s === s//true
let s2 = Symbol.for("")
let s3 = Symbol.for("")
s2 == s3//true
你可以通过以下方式获取Symbol的description(无需记忆)。
let s2 = Symbol("love")
let obj = {[s2]:"and peace"}
obj//{ [Symbol(love)]: 'and peace' }
Object.getOwnPropertySymbols(obj)//[ Symbol(love) ]
Reflect.ownKeys(obj)//[ Symbol(love) ]
Symbol定义了对象的许多实用方法,包括[Symbol.Iterator]、[Symbol.match]、[Symbol.replace]、[Symbol.split]、[Symbol.toPrimitive]、[Symbol.toStringTag]等。toStringTag方法甚至能改变对象的toString方法。
*BigInt ES6
在数字末尾加n可声明BigInt,其可操作大于Number所能表示最大数的数(2^53)。该类型尚在提案过程中,在新版Chrome与Node中得到实现。
带小数的运算会被取整。
let Num = BigInt(Math.pow(2,63))
let Num2 = 100n
Object
键-值对的无序集合。键(key)只能是字符串类型,值(value)可以是任意类型。'.'可以用于表示键路径,比如obj.key或obj.obj.a。
Object是引用类型,存储的是指针,而其他基本类型存储值。
let obj = {key:'value',
obj:{
a:'a'
}}
Array
用[]或new Array()创建,数组可以包含任意类型元素并且提供了相当多的方法。
Date
Date类型提供了丰富的与时间、日期相关的方法,Date()返回当前日期的字符串。
RegExp
正则表达式对象。
Map&Set ES6
ES6新增的数据结构。
Map是一组key-value对结构,key不能重复,否则只保留最新的值。与对象只支持string与symbol相比,Map 的key支持任意类型。
let students = new Map([['Lucy',90],['Peter',80],['Bill',85]])
students.get('Lucy')//90
students.has('Peter')//true
students.delete('Peter')//true
students.get('Peter')//undefined
students.set('Bill',90)//Map { 'Lucy' => 90, 'Bill' => 90 }
students.get('Bill')//90
Set类似集合,由一组不重复的key组成,否则只保留一个。
let foods = new Set(['Chicken','Noodles','Rice'])
foods.add('fish')//Set { 'Chicken', 'Noodles', 'Rice', 'fish' }
foods.delete('Noodles')//true
foods.has('Rice')//true
获取基本类型:typeof
typeof var
可以获取变量类型,不与ES规范一一对应,其返回值是以下结果之一:
返回数据类型 | 对应基础类型 |
---|---|
number | Number |
string | String |
boolean | Boolean |
object | Object Null |
undefined | Undefined(该变量不存在) |
symbol | Symbol |
function | Object |
bigint | BigInt |
获取实例类型:instanceof
instanceof用于判断检测对象的类型,包括"Array"、"Function"、"Object"及自定义类/构造函数等。
此外Object.prototype.toString.call()可以准确打印出Null的类型。也可以通过访问".constructor"获取构造函数判断类型。
let a = []
typeof a//'object'
a instanceof Array//true
class y{}
let t = new y()
t instanceof y//true
动态类型&类型转换
作为动态语言,JS允许同一个变量在不同时间用作不同类型。
使用JavaScript函数转换
例如全局方法(构造函数)String()、Number()、Date()以及变量的toString()方法等。不同类型还会有独有的方法比如Date变量的getDate()、getDay(),Number变量的toPrecision()等。
使用JavaScript自动转换
变量类型会根据需要发生类型转换,例如:
5 + null //5 因为null=0
5 + undefined //NaN 因为undefined转为数字是NaN
"0"+ null //"0null" 因为null="null"
"5" + 1 //"51" 因为1="1"
1 + "5" //"51" 因为1="1"
"5" - 1 //4 因为"5"=5
[1,2] + 1//"1,21" 数组先转字符串,再加"1"
if("str"){
//这里的代码将会执行,因为“str”可以转为true
}
可以观察到含字符串类型会转为字符串,没有或不能转字符串的话转数字(除加号以外,结果转数字)。
自动转换有一些基础规则,比如Boolean值的转换:true等于1,false等于0,空字符串、空数组和null等于0,非纯数字字符串转为NaN等。
有趣的是,"0"可以转为Boolean的“true”,但“0”转为数字0之后再转Boolean就会变成“false”。空数组也可以转“true”,转数字之后也为0。
基本类型&引用类型
JS通过引用操作对象,引用类型变量的复制不会引起对象复制(可以理解为复制指针),但基本类型变量的复制会在内存中产生两份变量。
let a={}; let b=a; a.x=0; //a===b
let c=5; let d=c; c=6; //c!==d
基本类型包装
除了Object类型存储的是引用,所有类型都是基本类型(存储值),但除了null和undefined,他们都像对象一样拥有自己的方法。这不是因为基本类型具有方法,而是在调用基本类型的方法时,JS引擎自动包装了基本类型,调用结束后销毁对象。
因此,向基本类型添加属性是无效的,因为添加完成后临时对象即被销毁,但可以向其原型添加属性和方法。
var str = 'str'
str.toUpperCase()
/*
相当于做了这些事
var _str = new String(str)
str = _str.toUpperCase()
*/
变量声明 Declaration
var
变量用var声明,不用var则作为全局变量。var声明的变量处于全局作用域或函数作用域。
变量提升 Hoisting
用var声明的变量,可以在声明语句之前使用,但不会初始化(赋值)。因此访问他们虽然不会报错,但会得到undefined。
let&const ES6
ES6中新增了let与const关键字,分别代表块级作用域中的变量与常量,同时不允许重复声明,没有变量提升。
TIP
const定义的对象并非常量,const仅保持变量的值(即指针)不变,如果要声明对象常量,则应该使用Object.freeze()。
块级作用域
由{}包裹的代码块。在for循环中,()与{}是父子块级作用域,也就是说{}用let或const声明的变量不会影响for循环计数。
块级作用域没有变量提升,可以防止在函数内使用上级变量时,后面声明的变量意外覆盖上级变量。
使用var声明变量:
var h=10;
function print(){
console.log(h)
var h;//覆盖了上级变量
}
print()//undefined
使用let声明变量:
var h=10;
function print(){
console.log(h)//暂时性死区
let h;//与当前作用域绑定,声明之前不可读取
}
print()//ReferenceError: Cannot access 'h' before initialization
同时块间的隔离有助于减少冲突和出错。此前,JS只能用函数作用域来隔离变量,常用的方式就是匿名立即执行函数(匿名IIFE)。
(function(){var hours=12}())
//等同于
{let hours=12}
暂时性死区 temporal dead zone
let和const声明的变量会与代码块绑定,在声明前不能使用同名的上级环境变量,否则会引发报错。
function
function关键字用于声明函数。
class ES6
class关键字用于声明类。
import ES6
import关键字用于导入其他模块的变量,这类变量被引用它的所有文件共享。
全局变量
var和function声明的全局变量会成为顶级对象(如window、global、self)的属性,而let、const、class、import不会。
判断变量是否存在
由于直接使用未声明的变量会报错,可以用try{}包裹代码,也可以用typeof variable == "undefined"
判断。
命名规范
变量名由26字母的大小写、数字、“$“和”_“组成,不能用数字开头。甚至支持中文,但不建议使用,避免引发麻烦。
函数 Function
函数都是Function对象的实例,相比普通对象,函数多了可被调用的特征。
'Function'与‘function’的不同
‘Function’是JS的内置对象,而'function'是一个声明函数的关键字。
和其他语言函数的区别
由于JS是弱类型语言,函数无法指定形参类型与返回类型,同时也无法限制传入参数的个数,因此没有重载的特性。函数内部可以通过arguments对象获取实参。
函数声明
推荐使用函数表达式let func=()=>{}
或let func=function(){}
为变量赋值,因此函数声明也遵循变量声明的规则。如果用function func(){}
直接声明函数,ES5中函数声明能完整地提升,ES6虽然规定了行为类似let,但实际可能会先赋值为undefined,不同环境可能有不同的处理。
参数传递
JS中任何参数都只能通过值传递,不存在引用传参。传递对象时,实际上复制了“指针”。因而对指针本身做修改不会反映到对象上,指针只用于访问对象,但本身不是对象。
那些支持引用传参的语言,修改引用同时也会修改对象,这是JS和他们的不同。
对象 Obejct
属性
普通对象属性(数据属性)拥有4个描述符(Descriptor),分别是Configurable、Enumerable、Writable、Value,前三项默认值为true,Value默认值为undefined。
Configurable控制属性特性(包括getter与setter,但value除外)能否被修改、属性能否被delete,且只能从true变为false。Enumerable控制属性能否被for-in遍历。Writable和Value字面意思。
访问器属性拥有2-4个特性,分别是Configurable、Enumerable、Getter?、Setter?。getter和setter的作用与其他语言一致。
属性描述符通过Obejct.defineProperty()、Object.defineProperties()、Object.getOwnPropertyDescriptor()写入和读取。
运算符 Operator
比较运算符
== === > < >= <= != !==
==表示在类型转换后相等,===表示类型和值都一样。除非需要用到==的特性,否则建议用===比较。
比较对象
对象的比较与原始值不同,比较的是引用,因此两个完全相同的数组不相等,除非他们是对同一处的引用。
赋值运算符
+= /= *= -= %= 等
和其他语言用法相同。
'与'运算符
&&
‘与’运算符,如果左边表达式的值是false或可以转为false则返回左边表达式的值,否则返回右边表达式的值。
Boolean角度:&&只有当两边都为true,结果才为true,如果左边结果为false,右边不会判断。
'或'运算符
||
‘或’运算符,如果左边表达式的值是true或可以转为true则返回左边表达式的值,否则返回右边表达式的值。
Boolean角度:||只有当两边都为false,结果才为false,如果左边结果为true,右边不会判断。
扩展运算符ES6
...
ES6中的扩展运算符,用在数组或对象前表示取出所有项或属性。
用于对象
let obj={a:1,b:2}
let newObj={...obj,c:3}//{ a: 1, b: 2, c: 3 }
用于数组
赋值
生成数组的拷贝。
let arr=[0,1,2]
let newArr=[...arr,3,4]//[ 0, 1, 2, 3, 4 ]
let arr_copy=[...arr]
解构赋值
结合解构赋值,它还提供了生成数组的方法。
let [...arr_copy]=arr
let [ar1,...ar2]=[0,1,2,3]
ar1//0
ar2//[ 1, 2, 3 ]
let [...ar3,ar4]=[0,1,2,3]
//SyntaxError: Rest element must be last element
一个分号引发的惨案
let val = func()//;
[a,b]=[b,a] 或 [a[c],b[c]]=[b[c],a[c]]
如果缺少分号,JS解析会出现错误,原因是两行连在一起也符合语法规则。这种情况下
用于函数参数
除了为数组赋值,还支持作为函数参数。
let arr=[0,1,2]
function add(a,b){
return a+b
}
add(...arr)//1
TIP
任何含Iterator接口的对象都可以通过扩展运算符转为真正的数组。详情见iterable接口。
基本运算
! + - * / % ++ --
可以用于各种类型间的运算,不限于数字。
位运算符
<< >> ~ & |^ >>>
和其他语言用法相同。JS内部虽然存储64位数值,但对程序员透明,位运算符的结果和32位数值运算的结果一致。
三目运算符
expression1 ? expression2 : expression3
等同于
((expression1, expression2, expression3) => {
if (expression1) {
return expression2;
} else {
return expression3;
}
})();
'expression'与'statement'
expression即表达式,是一段有值的语句,比如变量x,函数f或者其执行结果f()。其他语言中函数一般不能视作expression,但JS中函数是对象,这是特例。
statement意为声明,比如let a=10
,或者if(code){code}
,这些语句不返回结果,而是提醒处理器该做什么。
方括号
[]
属性访问器。最常用的是表示数组[1,2,3]
与数组下标arr[1]
,也可以作为对象的属性名obj['key']
,支持使用变量作为属性名obj[key]
(key不仅可以是符合规则的字符串,也可以是Symbol)。
点运算符
.
属性访问器。点运算符的功能是[]的子集,当属性名为常量时可以用于设置、获取对象属性obj.key
。
循环体 Loop
while
while(expression){}
do...while
do{statement}while(expression)
forEach
Array.prototype.forEach(function(currentValue, index, arr), thisValue)
由于是以传入函数的形式遍历,forEach无法使用return从外部函数体返回,由于是数组的一种方法,也不支持break跳出循环。
for…in
for(let item in obj)
for…in支持遍历各种对象,它的item是对象的key,总是string类型。这种方法不稳定,不同时候结果的顺序可能不一致。如果用于数组,还有一个问题是所有属性也包括数组元素以外的自定义属性。
for…of ES6
for(let item of obj)
首先,它没有for…in的缺点,其次,也没有forEach的缺点。for…of支持数组、字符串、Set、Map和其他有iterable接口的对象,但不支持普通对象(会提示is not iterable
)。
循环控制
在循环体中使用continue或break可以跳过一次循环/退出循环。对于嵌套循环结构,JS提供了label关键字标记循环。
label: statement
loop1: while (true) {
loop2: while (true) {
break loop1;
}
}
控制语句 statement
If语句
if(expressionA){}else if(expressionB){}...else{}
Switch语句
switch(expression)case expression:statement break;...default:
部分关键字 Keywords
new
从构造函数派生出对象,构造函数的this指向创建的对象。
delete
用于删除对象属性,不可用于删除对象。
throw
抛出异常。通常结合try...catch使用。
async&yield
被异步函数的一种写法需要。作为语法糖可以被链式调用替代。
错误处理 Error Handling
try...catch语法
try...catch
try {
//insert code here
a.b=c
}
catch(err) {
console.error("Error catched: "+err)
}
throw
抛出自定义错误。
try {
throw 'Number is too big'
}
catch(err) {
console.error(err)
}
finally
无论是否发生错误都会执行的语句。
try {
}
catch(err) {
}
finally {
}
Error对象与类型
如果是内置错误(非throw抛出),抛出的是一个Error对象。包括name和message属性。
错误类型(Error.name) | 触发条件 |
---|---|
ReferenceError | 引用了尚未声明的变量。 |
SyntaxError | 语法错误。 |
TypeError | 类型错误。如xxx is not a function 。 |
URIError | URI函数的独有报错。 |
rangeError | Number方法的独有报错。 |
EvalError | eval() |
参考目录
- this https://www.cnblogs.com/xiaohuochai/p/5735901.html
- JavaScript https://www.liaoxuefeng.com/wiki/1022910821149312
- ECMA Script 6 http://es6.ruanyifeng.com/
- Execution Context https://juejin.im/entry/58edde2761ff4b00581b93ff
- Symbol https://www.cnblogs.com/diligenceday/p/5462733.html
- MDN
- runoob