•  

                                    语法基础

                                    本文采用了最新的es6语法,可以放心食用.

                                    专业术语

                                    为了防止在文中反复解释一些术语,我将一些常用术语的解释放在了这里.这意味着你并不需要直接看这里.等之后遇到了这些术语再翻上来看才是正道.

                                    ECMAScript

                                    ECMA本来是指欧洲计算机制造商协会,不过这个组织现在没事就喜欢制定一些标准,其中ECMAScript就是其中一个标准。ECMA-262这个标准的语言,一般被叫做JavaScript。但是实际上JavaScript是对ECMA进行了扩充。

                                    node.js也是对ECMAScript标准的一个扩充。

                                    实际上JavaScript包含三部分

                                    字面量

                                    字面量就是代码意义上的常量,说白了就是可以放到赋值号右边的都可以叫字面量.这样子这个赋值表达式的值就如字面上一样,你赋值号右边写的是啥,值就是啥,非常容易理解.

                                    注意,我下面代码中赋值语句的右边是字面量,我写这个语句只是方便理解,别理解错了.

                                    函数与方法

                                    理论上对象的函数就叫做方法,但是JavaScript是纯面向对象的语言,万物皆对象.所以函数和方法并没有C++那种半面向对象语言那种严格.一般来讲,window对象的方法叫做函数,其他对象的函数叫做方法,不过也没有那么较真就是了.

                                    弱类型(动态类型)

                                    JavaScript是一种动态类型的语言,这意味着一个变量可以存放任意类型的数据。这样可以提高程序的灵活性。但是也会降低代码的正确性。

                                    注释

                                    JavaScript提供了单行注释和多行注释

                                    虽然JavaScript的标准没有提供文档注释,不过有些强大的编译器仍然可以识别并解析JavaScript的文档注释。

                                     

                                    关于结尾的分号

                                    JavaScript并没有强制要求你加上分号,也没有要求一定不加,一般情况下看自己喜好就行.

                                    不过以` ( 和[开头的代码,前头必须加分号

                                    如果代码前是一个函数调用,那么编译器就会把前面的小括号和后面的解析到一起去,所以必须加分号。

                                     

                                    双引号与单引号

                                    JavaScript不区分单引号和双引号,字符串可以随意用这两种引号,不像其他语言严格取分大小写。

                                    不过ESlint标准,要求字符串必须是单引号。

                                    输入与输出

                                    输出有两种常用方式

                                    输入可以使用prompt方法,这个会直接弹出一个弹窗,也就是说需要浏览器环境

                                    严格模式和非严格模式

                                    在脚本或函数开头写下这句声明就可以开启严格模式,之后会添加很多限制,比如不允许使用未声明的变量什么的。

                                    注意:这句代码只允许写在脚本或者函数的开头!

                                    let与const [es6]

                                    代码的加载

                                    如何在网页中加载脚本呢?常用的方法如下:

                                     

                                    Hello World

                                    浏览器环境下,直接打开浏览器,F12找到console就能看到。

                                    node环境下,用node命令执行语句。

                                     

                                    垃圾回收

                                     

                                     

                                    数据类型

                                     

                                    在JS中,基本数值类型包括:

                                    类型构造函数
                                    nullnull
                                    undefinedundefined
                                    string"hello world"Sting()
                                    number123Number()
                                    bigint [ES10]123nBigInt()
                                    booleantrue falseBoolean()
                                    symbol [ES6] 
                                    object{}Object()

                                     

                                    JS中的5大简单数据类型其实就是值类型,而object是引用类型

                                    boolean

                                    布尔类型只有true和false,跟其他语言一模一样。

                                     

                                    number

                                    数字的存储

                                    特殊的number常量

                                    几个特殊的常量

                                     

                                    判断非数值类型:

                                    如果非数值就会返回true。

                                     

                                     

                                    string

                                     

                                    undefined

                                    声明但是未定义的变量,都是undefined

                                     

                                    object

                                    object是所有对象的父类,所有引用变量都是object类型。

                                    object是引用数据类型,里面操作的都是对象的引用(指针)

                                    null

                                    null也是一种object类型的数据,但是这个代表空对象。

                                    实际上这个玩意就是JS设计时的bug,理论上来说null 的类型就是null,但是因为这个bug存在的时间已经非常长了,大家早都习惯了,而且修理起来很困难,现在我们一般把这种没法修的bug叫特性。

                                    JavaScript的特性之一就是typeof null === 'object'

                                    null instanceof Object == false

                                     

                                    所以我们为了准确判断null,可以使用以下的代码。

                                    因为null是唯一一个false并且typeof会返回object的类型。

                                    这时候就可以理解,为什么空对象返回是true,如果其他对象返回值也是flase,那么就无法来判断null了。

                                    bigint [ES10]

                                    在JS中,可以安全表示的最大整数为Number.MAX_SAFE_INTEGER,也就是9007199254740991,如果超出了这个值,运算会静默失败。

                                    可以看到,值已经无法再增加了。

                                    对于这种情况,我们可以定义bigint类型,来计算这种超大的数。

                                     

                                    注意!bigint不可以和number的数进行直接运算。

                                    比如:

                                     

                                    同样的,无法用+运算符转型为number

                                     

                                    symbol [ES6]

                                     

                                     

                                    symbol类型是专门为对象服务的,symbol可以定义对象的私有属性。

                                    定义symbol的时候,必须要用中括号!

                                    同时,symbol对于Object.entries、Object.keys和for……in不可见。

                                     

                                    除此之外,symbol属性是唯一的,多个对象生成的symbol是不一样的。

                                    如果有特殊的需要,可以使用symbol的全局注册表

                                     

                                    类型转换

                                    显式类型转换

                                    数值

                                    正常情况下,如果字符串里面是纯数字,那么这个方法就会返回字符串里面的值

                                    如果字符串里面有非数值型数据,那么返回NaN

                                    注意!有一些特殊的数值转换

                                     

                                     

                                    也可以用parseInt()方法,强制转化成整数。而且会去掉末尾的单位。

                                    注意:只能去掉末尾的。

                                    同样的,也有parseFloat方法。

                                    字符串

                                     

                                    布尔

                                    JS中只有5种数强转后会变成false。剩下的都是true。

                                     

                                    隐式类型转换

                                    在JavaScript中,隐式类型转换实际上是隐式调用了显示类型转换的方法。数值型是转换的最后结果,具体过程如下:

                                    image-20201114000143353

                                     

                                    所有的比较都会向下进行转换。比如说字符串和布尔比较实际上会转为数值类型。

                                     

                                     

                                    在转换的过程中,如果有一边出现了null或者undefined,则仅当两边都是null或者undefined之一时,才会判定true。

                                     


                                     

                                    我们不妨看一些例题

                                     

                                     

                                     

                                     

                                    布尔

                                     

                                    数值

                                    字符串

                                    同样的,模板字符串也可以转换

                                    特别的,空字符串将转换为0

                                     

                                    字符串拼接会发生隐式转换,任何和字符串拼接的变量都会变成字符串。除了对象。

                                     

                                    数组

                                    数组也是对象,所以数组第一步会转化成字符串。如果数组里面有字符串的话,会自动拼接。

                                    如果数组只有一个数值,那么转换成的字符串还能转换成数值型

                                    特别的,如果数组为空,将转换为空串

                                    特殊转换

                                    另外还有一些特殊类型的隐式转换

                                    包装类

                                    基本类型与对象的转换

                                    基本数据类型有时候也会转换成引用类型。

                                    首先看这个例子。

                                    这时你一定会奇怪,为什么我一个string还能有属性?而且还能正常打印出来?实际上这就是包装类。当你对一个基本数据类型进行属性方法等操作时,实际上内部会先new String(name)。然后调用方法new String(name).length。之后立刻销毁对象。

                                    也就是说虽然看上去对一个数值类型进行了对象的操作,实际上确确实实是生成了一个临时对象。

                                    要格外注意,临时对象使用完后立刻会销毁。

                                    本例中,首先生成一个临时对象并且给这个临时对象赋予属性color,之后对象被销毁。之后再次生成一个临时对象,此时这个对象是没有color属性的,所以返回值为undefined。

                                    基本对象执行方法

                                    虽说可以自动进行包装类,但是数字对象是不可以直接使用方法的。

                                     

                                    js中内置的包装对象

                                     

                                     

                                    数据类型的判断

                                    null与object

                                    为什么typeof null是object?

                                    实际上这是一个历史问题,在最早JavaScript版本中,数据有标记位和数据位组成。

                                    000:object

                                    001:integer

                                    010:double

                                    100:string

                                    110:boolean

                                    而null则是数据位和标记位都为0,因此会被判断成object

                                     

                                    在ES6阶段,也有人提案修复这个bug,但是没有通过

                                     

                                     

                                     

                                    运算符

                                    除法

                                    跟其他的语言不太一样,JavaScript的除法是保留小数的。

                                    如果想只取整数,可以使用数学函数来截取。

                                     

                                    等于与严格等于

                                    等于运算符==允许JavaScript的解释器进行隐式转换,而严格等于不允许。

                                     

                                    不等和严格不等

                                    同样的,==允许隐式转型,而!==不允许。

                                     

                                    void

                                    void在其他语言一般都用于代表方法的返回值类型为空,但是在JavaScript里面却是一种运算符。void运算符执行的表达式,返回值永远是undefined。

                                     

                                    void经常用于HTML中a标签。用来表示点击不发生跳转。

                                     

                                    除此之外,void还用于箭头函数,防止内存泄漏。箭头函数标准中,允许在函数体不使用括号来直接返回值。 如果右侧调用了一个原本没有返回值的函数,其返回值改变后,则会导致非预期的副作用。 安全起见,当函数返回值是一个不会被使用到的时候,应该使用 void 运算符,来确保返回 undefined(如un下方示例)。这样,当 API 改变时,并不会影响箭头函数的行为。

                                    !!

                                    !!可以把其他类型的数据快速转成bool类型。因为一个!就是把数据转化为bool并且取反,而两个就是把数据取反后再变回来,相当于直接把数据转换成了bool类型。

                                    instanceof

                                    我们可以用instanceof运算符来判断两个两个对象间的关系。如果 A instanceof B == ture代表A是B的实例对象。

                                    typeof

                                    可以来查看数据类型。

                                     

                                     

                                    字符串

                                    创建

                                     

                                    模板字符串 [es6]

                                    模板字符串是超级无敌强化之后的字符串,用间隔符(ESC下面那个符号)来引用.

                                    下面简述它的两个主要功能

                                    1. 可以在字符串内直接使用变量,并计算

                                    2. 可以在字符串内保留回车,不用自己拼接回车了.

                                    当一个字符串拼接的过长时,原来那种+号的写法过于繁琐,所以可以使用模板字符串,直接把变量放在里面

                                    不仅仅是这样,模板字符串里面的变量也可以进行运算,并且保留回车.

                                    字符串的解构

                                    可以把字符串结构成一个数组,每一个数组的元素代表字符串的一位。

                                     

                                    数组

                                    数组与对象

                                    在JavaScript中,数组是对象的一个子类。实际上数组就是一个特殊的对象,数组的key就是其下标,value就是数组元素。

                                    注意数组和一般的对象有一个不一样的地方,就是它会把a['3']=1自动转型成a[3]=1

                                     

                                    数组的创建

                                    1. 使用字面量

                                    1. new一个

                                     

                                    数组的解构 [es6]

                                    指得是可以把数组里的内容一次批量导出并赋值。

                                     

                                    数组元素增加

                                    手动改length

                                    修改完之后,末尾会出现几个空的元素。默认用empty填充。

                                    用下标动态添加

                                    同样的,如果修改的下标过大,中间就会有很多地方元素为empty。

                                    push

                                    在数组尾部添加元素。

                                    unshift

                                    在数组最头头添加元素。

                                     

                                    数组元素的删除

                                    delete

                                    可以看出来,delete删除之后数组长度不变,只是被删除元素被置为undefined了。

                                     

                                    pop

                                    删除最后一项

                                     

                                    shift

                                    删除第一项

                                     

                                    数组的遍历

                                    forEach

                                    forEach可以使用回调函数来操作数组的数据。回调函数里面的参数,就是数组里面的元素。

                                    判断数组相等

                                     

                                    函数

                                    简介

                                    JavaScript的函数就是对象,万物皆对象.但是JavaScript是弱数据类型的,所以在使用函数的时候和别的强数据类型语言还是有差别.

                                    就比如说,函数不用写返回值类型,没有返回值也不用 return void; ,并且定义时需要显式定义.

                                    函数的创建方式

                                    赋值表达式创建

                                    这种表达式的右边是一个匿名函数。

                                    直接创建

                                    用Function构造函数创建

                                    不推荐使用。构造函数需要用Function来创建函数,Function里面的参数就是函数体,需要用引号括起来,但是里面没有提示,非常不好用。

                                    另外用构造函数创建函数时,new Function()的new可以省略。

                                    箭头函数 [es6]

                                    这个东西看着复杂,实际上就是一个简化版匿名函数,

                                    我们可以用匿名函数来参考着看.

                                    箭头函数还可以进一步简写:

                                    函数参数的特性

                                    函数参数匹配

                                    JavaScript语法非常自♂由,允许调用参数时,参数不用匹配。

                                    可以看到,如果参数传的太多,那么JS不会去接受多出来的实参。如果太少,那么就把没有赋值的形参默认为undefined。所以1+undefined==NaN

                                    arguments

                                    JS的函数默认有一个重要的参数,那就是arguments。在函数调用的时候,所有的实参都会传到arguments里面。这个arguments对外隐藏,跟this一样。

                                    可以看到arguments把实参全都接收了,而且数据的格式很像一个数组。其实arguments就是一种伪数组。它可以通过下标进行访问,并且拥有length属性。但是没有数组的方法。

                                    …rest和…spread

                                    rest和spread就像是反函数和函数。rest的意思的剩余的,spread则是展开、摊开的意思。这两个语法都会用到扩展运算符,这种特殊的运算符。

                                     

                                    在函数传参的时候,有时候函数的参数数量是不确定的,这时候就可以使用rest参数。rest参数会统一获取剩下所有参数。

                                    可以看到,其实rest参数会把传进来的所有参数封装成数组。相当于收尾工作,所以这个参数只能写在最后面。

                                     

                                     

                                    spread和rest刚好相反,rest是来打包参数的,而spread可以把一个数组拆开成为一堆零散的数据。

                                    这时候,函数接受了几个就能用几个,多余的参数只能去arguments里面找。

                                    而且spread仅仅是解构数组,所以任何位置都可以写。

                                     

                                    默认参数(ES5)

                                    在ES5中,我们可以通过以下方式来模拟默认值。

                                     

                                    但是这个写法也是有着bug存在的。

                                    我现在需要给a,b传入0,0的值。但是0被隐式转换为了false。最终还是变成了默认值。

                                    我们不妨做出以下改进。

                                     

                                    默认参数(ES6)

                                    ES6中,可以直接给参数赋值。

                                     

                                    并且和java不一样,js的默认参数之后可以跟普通参数。调用时,如果想使用默认参数,需要写undefined。

                                    默认参数对arguments的影响

                                     

                                    数组添加元素

                                    1. Array.push()

                                    2. Array.unshift()

                                    3. Array.length

                                     

                                     

                                    函数返回值

                                    如果你没有自己写的话,默认为undefined。

                                     

                                     

                                     

                                    立即执行函数

                                    把一个匿名函数前后都用小括号括起来就变成了立即执行函数,这种函数只能用一次,一旦程序运行到这里立刻执行.

                                    因为没有名字,之后无法被其他程序调用.

                                    函数的length

                                    函数的length就是其参数的个数。几个参数length就是几。

                                     

                                     

                                    回调函数

                                    回调函数指的是那些由我们自己定义,但是由系统自动调用的函数。

                                    上面这个例子中,我们定义了一个匿名函数,但是这个函数自动被callBack函数调用执行了,这种被自动调用的函数就是回调函数。

                                    eval函数

                                    eval简直太强大了,js的这个函数,可以让你输入一个字符串,并且直接运行你字符串里面的代码。

                                     

                                    name属性

                                     

                                    apply,call和bind方法

                                    bind方法就是把所执行的函数做了一个打包,而不是直接运行。

                                    可以看到,实际上bind就是把obj.show()这个函数进行了一个保存,不需要每次都去apply和call了,直接可以用一个新的函数去执行。

                                    apply最多可以接收两个参数:this对象和参数数组

                                    call则可以接受多个参数:this对象,和一连串参数。

                                    [[Call]]和[[Construct]]

                                    js函数的内部有两个不同的方法。当通过new关键字调用的时候,执行[[Construct]]方法。如果没有new则调用[[Call]]方法。注意,不是所有的函数都有[[Construct]]方法,比如箭头函数就没有。

                                    通过这两个属性,我们就可以判断一个函数是否是被new出来。

                                    其中如果你使用new来创建对象,new.target就会被赋值为其new的构造函数。如果没有用new,那么就会赋值为undefined。

                                    这个new.target被称为元属性。元属性是指非对象的属性。new就不是一个对象,但是它仍然拥有target属性。

                                     

                                     

                                    块级函数

                                    在ES5中,如果在

                                    高阶函数

                                    如果一个函数的参数或者返回值也是一个函数,那么这就是一个高阶函数。

                                    最有代表性的高阶函数是数组的filter、map和reduce

                                     

                                     

                                    对象

                                    万物皆对象

                                    JavaScript只有对象,甚至没有类的概念,可以说是真真正正面向对象的语言。但是很快大家就发现了,虽然说着没有类,但是没有又不行,只能用各种方法间接实现。最终在ES6中引入了类的概念。

                                    记得我在开头说过的吗?JavaScript中不区分函数和方法,因为所有的一切都是对象,方法也是函数对象。

                                    对象的分类

                                     

                                    对象的创建方式

                                    字面量创建

                                     

                                    用Object创建

                                    一般不怎么用这种

                                     

                                    构造函数

                                    构造函数用this来添加属性,其实原理和Object一样的。首先new的时候,this就指向了new出来的内存空间。之后给内存赋值,最后把这个内存起始地址返回给LeiMu,这样就通过this,间接完成了属性的添加。

                                     

                                    工厂模式

                                    一种设计模式,这样写出来不用加new

                                    实例化类

                                    ES6特有。

                                     

                                     

                                    对象属性和方法简写 [es6]

                                    如果在对象中的参数已经在外界被定义过了,那么可以直接写变量名,而不用再去写键值对。

                                    如果不想用这个变量名,可以按照原本的语法来写。

                                     

                                     

                                    而方法简写

                                    对象的解构 [es6]

                                    指得是可以把对象的键批量导出的一种语法。在export中用得很多。

                                    其实这里面就用到了属性的简写,实际上的代码应该是这样。

                                    首先var了一个对象,这个对象有两个属性,也就是左边的name和age,而这两个属性又解构的loli被传入了两个参数,分别是右边的name和age。

                                    如果不是很理解可以看这个代码:

                                    也就是说右边的值是自定义的,也是需要被传入数据的。

                                    对象的展开

                                    对象的展开就是指把一个对象展开成一个个的键值对,一般用于把两个对象直接合并。看一下例子就明白了。

                                    可以看到,CPP通过...的语法,可以把c和java的属性直接解析出来并且把结果传入CPP对象中。

                                    如果不用这种展开的语法会怎么样呢?

                                    可以看到,这个其实直接把指针传进去了,而不是传的值。

                                    这种语法其实可以理解为一种多继承,在本例中C++继承了c的指针和java面向对象的思想。所以以后使用多继承可以考虑这种对象的展开语法。

                                    对象的数据的访问与添加

                                    对象的数据可以用点运算符来访问,也可以用下标运算符来指定访问的key。

                                     

                                     

                                    也可以用下标运算符修改指定key的value。如果这个key不存在,那么对象会自动添加这个键值对。

                                    也可以用成员运算符来添加key

                                    .name的语法被叫做属性访问["name"]的语法被叫做键访问

                                    with

                                    with可以来批量修改对象的属性或方法

                                    使用with前:

                                    使用with后:

                                    但是不推荐使用with,因为with可能会导致很多bug。比如下面这个

                                    可以看到,我明明把loli和book对象传了进去,但是最后只有loli对象正常输出了,这是为什么?其实当book对象进入with代码块之后,发现直接对一个未声明的属性赋值,按照编译器的规定,一切未声明的属性全都归属window对象。所以这个name和age实际上被添加到了window对象上面去。

                                     

                                    删除对象属性

                                     

                                    get与set

                                     

                                    跟c#的一模一样,很普通的语法。

                                     

                                    属性描述符

                                    获取与修改

                                    其实JavaScript中,每个对象的属性都有很多属性,包括是否只读,是否可枚举等。

                                     

                                    数据描述符

                                    writable

                                    writable决定了该属性是否可以被修改,如果设置为false,那么该属性就无法被修改。

                                    可以看到,虽然代码没有报错,但是该值没有修改成功。

                                    如果是在严格模式下,代码会报错。

                                    configurable

                                    这个玩意决定了你是否能使用defineProperty函数,如果为false,那么对该属性使用defineProperty会不起效果。而且,如果你再写configurable:true会直接报错。也就是说如果把configurable:false设置了之后,那么操作不可逆,不能再设置回来。

                                    但是这个操作还有两个特殊的地方

                                    1. 即使把属性设置了configurable:false,我们还是可以把writable设置为flase,但是无法设置为true。

                                     

                                    这个属性也决定了该属性是否可以被delete删除

                                    enumerable

                                    可否枚举,也就是说在for……in的时候,是否可以被打印出来。在循环与迭代一章会详细讲解。

                                     

                                    访问描述符

                                    set与get

                                    如果给某个对象定义了get或者set属性,那么这个属性就会变成访问描述符,JavaScript会忽略它们的value和writable属性。

                                    有两种方法可以来设定getter和setter。

                                     

                                    可以看到,实际上getter和setter都是通过创建一个新的变量来完成对私有数据的封装的,但是实际上这个变量并不是私有的,还是可以通过girl._age_girl._name_来进行访问的。

                                    但是这样子在心理上似乎能感觉好一点,至少大部分人都知道下划线开头的变量是私有变量,不应该随意访问。

                                     

                                    类 ES5

                                    创建

                                    虽然我很不喜欢用ES5,但是RPGMakerMV,人家用的是ES5的语法,而且一眼望过去,成片成片的原型链,各种骚操作。太难了。

                                    话不多说,ES5没有直接的类继承什么什么的,必须依靠显示原型来间接完成类的继承与创建。

                                    复制继承并重写父类的某一个方法

                                     

                                    继承

                                     

                                    静态类

                                    核心的设计思想就是直接在构造函数的对象上面添加属性和方法。

                                     

                                     

                                     

                                    类 ES6

                                    特性

                                    类的创建方式

                                    ES6中,可以用类来创建对象,因为ES6真的很香,跟java他们用法差不多,现在你就能发现ES6有多香了。ES5学起来简直难受的一批,浑身不舒服.

                                    可以看到,程序正常运行:

                                    image-20200831233256840

                                     

                                    类的继承

                                    用extends表示继承哪个类,之后用super来指定继承的属性,默认继承全部的方法.

                                    可以看到程序正常运行:

                                    image-20200831233416082

                                     

                                    重载

                                    JavaScript没有重载,只有重写。

                                    实例方法和实例属性

                                     

                                    静态方法和静态属性

                                    在前面加上static就行了,只能通过类来调用。

                                    静态类

                                     

                                     

                                    词法作用域和执行上下文

                                    编译基本过程

                                    V8引擎在处理js代码的时候需要用到3个组件:

                                    1. 解析器(parser):用于把js代码解析成抽象语法树AST。

                                    2. 解释器(interpreter):将AST解释成字节码(bytecode)

                                    3. 编译器(compiler):将字节码转换成机器码

                                    分词/词法分析

                                    在这个过程中,解释器会把代码分成一个一个有实际意义的代码块,也就是所谓的词法单元

                                    比如说var a = 2;就会变成var a = 2 4个单独的词法单元。

                                    语法分析

                                    这个阶段,会把刚才的词法单元转化成一棵抽象语法树

                                    image-20201113173031891

                                    解释执行

                                    将高级语言转化为机器语言

                                    LHS和RHS

                                    这个就是左值和右值的关系,其他语言虽然也有,但是在编译原理这里意思略有不同。

                                    LHS代表左值查询,例如a=1,这种带赋值语句的表达式,就会调用左值查询,因为这种赋值语句会实实在在更改内存数据,所以左值查询实际上是去查找a所在内存。

                                    RHS虽说是右值查询,实际上代表的是所有非左值查询,比如console.log(a) function f(a){}。右值查询需要去查找a这个变量的值究竟是多少,而不关心内存什么的,他的任务就是查到a的值多少。

                                    比如下面这个代码

                                    这个代码一共包括3个LHS和4个RHS

                                    1. f(2)执行,进行实参传递,隐式赋值a=2,执行1次LHS

                                    2. var b=a,首先要对a进行RHS,找出其值。之后执行赋值语句LHS。此时执行2次LHS,1次RHS

                                    3. return a+b首先要去查找a和b的值,调用2次RHS,然后return。此时执行2次LHS,3次RHS

                                    4. var c = f(2)首先去查找f(2)return的值,调用一次RHS,最后赋值调用LHS。此时执行3次LHS,4次RHS

                                     

                                    执行上下文

                                    产生

                                    执行上下文储存着程序运行所需要的各种变量。

                                    四种情况会生成执行上下文:

                                    执行过程

                                    执行上下文分为全局执行上下文函数执行上下文。说白了这个就是预编译的作用域

                                    在预编译前,首先会把window确定为全局执行上下文对象,之后进行预编译。对于函数也是如此,在执行函数前,首先会把要执行的函数确定为函数执行上下文对象,然后进行预编译。

                                    注意!定义函数并不会产生执行上下文对象!只有调用才会产生。

                                    也就是说在js执行中,每调用一次方法,就会产生一个执行上下文对象。那么js是如何管理这些执行上下文的呢?实际上,js会用一种执行上下文栈来储存管理这些对象。

                                    在代码即将执行时,首先就会把全局执行上下文入栈,之后每进行一个方法调用就会把当前正在执行的函数执行上下文入栈,执行完毕时出栈。如果函数嵌套调用的话,就按照调用顺序依次入栈。

                                    inner函数执行上下文 <-栈顶

                                    outer函数执行上下文

                                    全局执行上下文

                                     

                                    变量提升与方法提升

                                    JavaScript为了提高性能,会把代码直接预编译,之后再解释。预编译分为全局预编译和局部预编译,全局预编译发生在页面加载完成时执行,而局部预编译发生在函数执行的前一刻。对应着全局执行上下文和函数执行上下文。

                                    所谓的全局预处理实际上干了这么三件事:

                                    1. 把var定义的全局变量添加为window的属性,并把值设置为undefined。

                                    2. 把function声明的全局函数添加为window的方法,并且把这个函数对象指向对应的堆内存。

                                    3. 把this指向window对象

                                    这就是所谓的变量提升方法提升

                                     

                                    局部预编译略有不同:

                                    在执行函数体之前会进行函数的局部预编译。

                                    1. 给形参赋值,并把形参添加为函数执行上下文对象的属性。

                                    2. 给arguments赋值,建立一个伪数组,并把这个添加为上下文对象的属性

                                    3. 把var声明的变量添加为属性,并且把值设置为undefined。

                                    4. 把function声明的方法,添加为上下文对象的方法

                                    5. 把this指向调用当前函数的对象。

                                     

                                    理论的东西虽然很重要,但是不举个例子实在是过于抽象。

                                    看看下面这两个代码,它的执行结果是什么?

                                    答案是:第一个是2,第二个是undefined。

                                    这个看上去虽然很抽象,但是结合上面的理论来看的话,就能够理解了。

                                    首先把var声明的变量提到最头头,并且赋值为undefined。然后顺序执行代码。

                                    也就是说,实际上代码执行起来应该是这个样子。

                                     

                                    其次,方法也是会提升的。注意下面两个代码。

                                    第一个会正常打印结果,但是第二个则会报错。

                                    实际上函数提升只限于function声明的,函数赋值表达式本质上还是一个var声明的变量,所以不会提升。实际上他们执行的代码如下:

                                     

                                     

                                    另外要注意!先执行变量提升,后执行方法提升。也就是说,如果变量名和方法名重名,那么方法会把变量覆盖掉。

                                    如果对变量进行了赋值操作,那么就会重新把变量覆盖回来。

                                     

                                    还要注意,JS中var会无视块级作用域。

                                    let、const与临时死区

                                    要注意,let是不会变量提升的,如果对let提前引用的话,会报错。

                                    这是为什么呢?实际上JS引擎有一个被称为临时死区的东西。

                                    JavaScript引擎在进行预编译的时候,如果遇到var 声明的变量,就提升至作用域顶部。如果遇到let和const则放到临时死区(temporal dead zone)里面。访问TDZ中的变量会触发错误。只有执行了变量的声明之后,变量才会从TDZ中移除。

                                    如果访问的变量不在TDZ中,则显示undefined。

                                     

                                    全局作用域的绑定

                                    var还有一个致命的特点就是会覆盖全局作用域的变量。

                                    就是这样,如果把一个变量声明两遍,那么之前那个会被顶替掉

                                    let声明的变量则直接报错。就算之前那个是var也照样报错。

                                    如果在局部区域用let定义了同名变量,那么只会覆盖原变量,而不会替换。跟C语言差不多。

                                     

                                     

                                    词法作用域

                                    词法作用域其实就是指得作用域。作用域有三种:

                                    全局作用域和函数作用域

                                    顾名思义,在js文件中,整个都是全局作用域。在函数内部的是函数作用域。

                                    在函数作用域的变量,不会泄露到外界,可以有效封装。

                                     

                                    块作用域 [es6]

                                    JavaScript本来没有块作用域,但是ES6之后新增了块作用域。可以用let声明局部变量。但是var声明的变量还是会忽视块作用域。

                                    特别注意:java,C++之类的语言是有块级作用域的,别到时候学了JavaScript不会写java了。

                                    作用域链

                                    查找一个变量的时候,是先查找当前作用域,之后查找上一级,这样的查找顺序很像一个链表,因此叫做作用域链。

                                    不过要注意,作用域链的查找是按照函数作用域来进行的,而不是块作用域,最终会抵达全局作用域。

                                    可以看到,虽然块作用域也定义了a变量,但是直接被忽视了。

                                     

                                    如果是函数作用域就不会被忽视。

                                     

                                    访问未定义变量

                                    如果所使用的变量未被定义,有可能触发ReferenceError异常。

                                    是否触发跟LHS河RHS有关系。

                                     

                                    如果你直接对一个未声明的变量使用RHS,那么查询会一直抵达全局作用域,如果还没有找到,那么就会抛出异常。

                                     

                                    如果你对一个未声明的变量使用LHS,那么在“非严格模式”下,引擎就会在全局作用域下创建这个变量。

                                    编译器会沿着作用域链一直查找a的定义,结果直到全局作用域都没有找到,所以最后在全局作用域创建了一个a。此时这个a就是全局作用域的变量了。

                                     

                                    作用域与执行上下文

                                    预编译

                                     

                                    1. 创建全局执行上下文。并把全局上下文压入执行上下文栈。

                                    2. 找到所有全局作用域中var声明的变量。并放到全局对象中。

                                    3. 找到所有的函数声明。并放到全局对象中。

                                    4. 找到let、const和class声明的变量。并放到全局scope中。

                                     

                                    image-20210328104445149

                                    这是我在chrome掐断点的结果,可以看到chrome中把全局对象叫Global,而把scope叫做Script。

                                    注意在访问变量时,首先会访问全局scope,之后再访问全局对象。也就是说先访问let和const的再访问var的

                                     

                                    1. 做名字重复的处理

                                    彼此间名字不能重复,而且var和function重名的话,function优先

                                    1. 登记并把var声明的变量初始化为undefined。

                                    2. 把全局作用域的function登记,并初始化为函数对象。

                                    1. 把块级作用域的function登记,但是初始化为undefined。

                                    1. 登记let、const和class的变量,但是不进行初始化

                                    我们把这种let提升后的变量叫做临时死区(temporal dead zone)。因为虽然这些变量进行了变量提升,但是还是不可使用。

                                    1. 执行语句。

                                    语句执行

                                    我们以下面的代码为例:

                                     

                                    函数原型

                                    这个是函数最重要的地方

                                    函数,对象,函数对象和实例对象

                                    函数对象其实就是函数指针,保存着这个函数在内存中的地址。

                                    上面这个代码中,Student就是函数对象,保存着Student这个函数在堆中的地址值。

                                    Student()是函数,代表编译器去访问并执行Student指针所指向的内容。

                                    如果0用这个函数去构造对象,那么这个XiaoMing就是实例对象。其实就是类和对象的关系。

                                     

                                    其次,要注意,函数对象是可以new的,也就是可以有子类的。而有一些静态对象是不能new的,那些静态对象就不是函数对象。

                                    prototype(显式原型)

                                    每个函数对象都拥有prototype属性,这个实际上是一个指针,指向一个被称为原型对象的对象。prototype就是显式原型的指针,可以直接通过属性的方式去访问。

                                    如果这个函数对象是新定义的,那么这个prototype默认指向一个空对象。

                                    这个空对象是在定义函数时就创建的,相当于下面这个代码

                                    显式原型默认是一个空对象,但是这个空对象并不是完全空,它里面也有两个默认值constructor和__proto__。

                                    constructor非常特殊,因为constructor指向Student函数对象,而函数对象的prototype又指向原型对象。刚好是一个双链表的结构。

                                    image-20201028165057282

                                    我们可以来直接操作显式原型,为里面添加方法和属性。并且,在显式原型中添加的方法和属性可以直接在实例对象中使用。

                                     

                                    __proto__(隐式原型)

                                    所有函数都有隐式原型和显式原型属性,但是实际上这两个都是指针类型,都指向函数的原型对象。但是函数对象更强调显式原型的属性,而实例对象更强调隐式原型属性

                                    一旦一个实例对象被创建,其隐式原型就会自动指向构造函数的显式原型。这就导致实际上Student构造函数的prototype和实例对象小明的__proto__,实际上都指向了Student的原型对象。

                                    实际上在实例对象被创建时,编译器会自动执行这句代码

                                    隐式原型链

                                    在拥有了以上基础的情况下,我们就来思考这样一个问题。在刚才为显式原型添加了方法后,为什么可以直接就用实例对象调用了?究竟是如何调用的?其实这个就是原型链。

                                    首先,实例对象在调用方法或属性时,首先会去查看自己有没有这个方法。如果有就直接用了。

                                    如果自己没有手动定义方法(其实就是重写),那么就回去调用构造函数提供的方法。

                                    其次,如果自己没定义,构造函数也没有,那么紧接着就回去查看__proto__属性所指向的原型对象,调用原型对象的方法

                                    如果Student的原型对象也没有,那么就会去原型对象的对象找。直到抵达原型链的尽头,如果到了尽头还没有找到,就会报错说not defined。

                                    因为这条原型链一直是依据__proto__来查找原型对象的,所以也叫做隐式原型链

                                     

                                    记住 实例对象的隐式原型指向其构造函数对象的显示原型对象。

                                     

                                    隐式屏蔽

                                    刚才我们说到,编译器会沿着原型链去查找数据,但是如果修改这个值会如何呢?

                                    实际上,如果这个值在原型上的话,就会发生隐式屏蔽的bug。

                                    这种bug原理其实很简单,编译器在处理小明.a+=1的时候,首先会通过原型链找到Student.prototype.a,之后进行a+=1。最后在小明这个对象中新建一个属性a,把刚才赋值的结果再赋值给小明.a

                                    总结一下:

                                    如果想避免这种情况,请务必直接写Student.prototype.a+=1!!!

                                    原型链的结构

                                    刚才我们只看了原型链调用数据的过程,现在我们来仔细看一下整个原型链的整体结构。

                                    Function链

                                    首先我们先定义了Student函数,而这个定义的过程function Student() {}相当于var Student=new Function(),也就是说我这个Student函数对象本身就是一个实例对象,所以同时拥有prototype__proto_属性,prototype默认指向空对象(下文会说到),而__proto__则指向了其构造函数Function的原型对象。

                                    Function是什么?Function本身就是用来构造其他函数的函数对象,Function()就是构造其他函数的构造函数。那你自己也是一个函数啊?那你这个函数是谁构造的呢?

                                    答案是Function的构造函数就是自身,也就是说:

                                    没错,Function非常特殊,它自己就是自己的构造函数,他自己就是自己的实例对象,所以他的两个原型属性相等,指向了Function的原型对象。

                                    那么Function的原型对象是什么?Function的原型对象就是一个对象,所以它由new Object产生,这就代表,Function的隐式原型指向构造函数Object的原型对象。

                                    而Function.prototype没有prototype了,因为他并不是一个函数对象。

                                    这样,这条链就完了。

                                    Object链

                                    刚刚说到,创建一个新的函数对象Student时,其prototype默认指向空对象。这个空对象的构建过程是什么呢?首先需要new一个Object,那么既然是new出来的,那么这个Object()就是一个构造函数了,那么刚才我们说过,一切函数都是由Function构造函数创建的,所以

                                    之后,Object本身肯定也有一个prototype属性,它指向Object的原型对象。这个原型对象非常重要,里面定义了toString,VauleOf等重要方法。而Object.prototype.prototype==undefined,很简单,因为Object的原型对象本身并不是一个函数,所以没有prototype。但是所有对象都有隐式原型,这就麻烦了。你Object的原型对象也是一个对象,如果让他也默认指向一个空对象,那么那个空对象也会有一个隐式原型,就会指向下一个空对象,导致无限递归。

                                    为了避免这种情况,我们规定:

                                    这样这条链就完结了。

                                    实例对象链

                                    首先我要强调,只有函数对象才有prototype,普通的实例对象的prototype==null

                                    小明作为实例对象,拥有__proto_属性,指向其构造函数的prototype

                                    还记得我刚刚说过的吗?Student的prototype在一开始就被创建了,相当于new了一个空对象。那么既然是被new出来的空对象,那么也就是一个实例对象喽,既然是实例对象,那么这个Student的原型对象也拥有一个__proto__,指向Object构造函数的原型对象。

                                    也就是说:

                                    Student.prototype是一个普通的实例对象,所以没有prototype。

                                    这样这条线也完了。

                                    总结

                                    image-20210327170817052

                                     

                                     

                                     

                                    this

                                    精通this是你从JS萌新变成JS巨佬的必经之路,但是想熟练掌握这个东西,并不是一件轻松的工作..

                                     

                                    this的指向由其调用函数的上下文决定的。

                                     

                                     

                                    下马威

                                    不要说你很懂this,在java或者其他高级语言里面,this的指向或许比较简单,一般都是指向类的实例对象。但是在JavaScript中,this真的是要人老命。

                                    首先来看看这个代码,看看结果和你想的是否一样。

                                    答案是0。add的num根本就没有增加!

                                    原因其实也不是很简单,首先add.num = 0;这句代码确确实实为add增加了一个num属性,但是我们要注意,add()这句代码实际上是缩写的,完整版应该是window.add()。也就是说,此时this又变成了window,而window没有num属性,所以说this.num++;的时候,window对象动态生成了一个num属性,并且++。

                                    总结一下,在执行add()之前,只有add有num属性。而在执行add();的时候,this指向发生了变化,指向了window。而window对象没有num属性,所以动态生成了一个num属性,并且++。

                                     

                                    当然解决这种bug的方法也很简单,第一种是在函数内部用函数名去访问属性。

                                    第二种就是让this强行指向add。

                                     

                                    如果你不想犯这种错误,建议平时就不要用this,或者说仔细看看我下面的讲解。

                                    this与函数调用栈

                                    this其实和全局执行上下文是紧密相关的。其实就是函数的调用栈。this永远指向栈顶元素。

                                     

                                    如果以函数的形式调用this,那么this就是调用对象。

                                    因为这个调用栈为:小丛雨->show

                                    然后从show开始,沿着栈去查找上一层指针,也就是小丛雨。

                                    可以看到,this就是调用该方法的对象。

                                    比较特殊的是,所有全局函数都是定义在window对象上的,所以全局函数的this都是window

                                     

                                    this当然不可能这么简单,来看点难的吧!在回调函数中,this将会怎么样呢?

                                     

                                     

                                     

                                     

                                    在构造函数中

                                    首先来看看执行构造函数的流程:

                                    1. 创建一个新的对象

                                    2. 链接原型

                                    3. 绑定this

                                    4. 如果函数没有return语句,那么在new表达式中,会自动return this

                                    也就是说实际上代码应该是这个样子的

                                    另外,在调试的时候可以发现,在创建这个对象的时候,首先会开辟一片内存空间,this会一直指向这个内存空间。之后Girls把this直接return了,赋值给了LeiMu,所以之后LeiMu也指向了这个空间,这样就完成了地址的接力棒。

                                    调用call和apply时

                                    call方法里面是谁,this就指向谁,这个最简单。

                                    super的指向

                                    super指向其父类的显式原型。

                                     

                                    let与this

                                    let声明的变量并不依附于this

                                    注意!在node环境下,this.b 也是undefined

                                     

                                     

                                    全局环境下的this

                                    默认指向window

                                    在严格模式下,undefined

                                     

                                     

                                     

                                     

                                    存在上下文时

                                     

                                    因为第三个return fn() 的时候,这个fn()其实是window调用的,obj3调用了fun,而fn由window调用。因此是undefined

                                     

                                    现在问题来了,如果想要obj2返回2该怎么办?

                                    如果不用apply之类的话,可以

                                     

                                     

                                    构造函数与this

                                    构造函数中,this分为两种情况

                                    箭头函数的this

                                    箭头函数的this会根据上下文决定,并不一定是window

                                    this的优先级

                                     

                                     

                                    闭包

                                    闭包的创建

                                    当一个内部函数使用外部函数的数据时,就产生了闭包。

                                    下面这种情况就不会产生闭包,因为函数的定义并没有被执行,此时必须要调用inner才能产生闭包。

                                    说白了,就是函数的私有数据被外界访问了,那么就会形成闭包。

                                     

                                    闭包实际上是一种内存泄漏,因为一旦outer函数的数值被占用,就无法被垃圾回收,所以其实就是内存泄漏。

                                    闭包的作用

                                    实现静态局部变量

                                    闭包可以让外部函数的局部变量不会被释放,相当于续命(膜)。利用这个特性,可以来间接实现静态变量。

                                    可以看到,a这个局部变量没有被释放,一直在内存中。间接实现了静态变量。

                                    并不是所有的变量都不会被释放,只有被内部函数使用的数据才会保留,剩下的数据都会被释放,包括add这个变量。之所以还能执行add方法,是因为out保留了add方法的指针,这才使add的内存没有被回收。

                                    回调函数

                                    回调函数经常会使用闭包,原理就是回调函数会调用外部数据。

                                    只要使用了回调函数,就使用了闭包!

                                    模块化(对象导出)

                                    使用闭包可以实现模块化。

                                    模块化(添加window属性)

                                     

                                    for循环与闭包

                                    首先看这段代码,猜猜看执行结果是什么。

                                    for执行5次,每隔1s打印一个数字,5秒刚好打印完5个。你是这么想的吗?

                                    实际上打印结果将是5个5。

                                    其实很好理解,for循环终止的时候,i 刚好等于5。然后回调函数过了1秒以后又回来取得i的值,此时 i 已经是5了,所以不论过了几秒,都会打印5。

                                    这个代码之所以没有按照我们的思路来执行,是因为实际上他们共享了一个作用域,而我们希望他们各自的 i 都有自己的作用域。解决方案如下:

                                    1. 使用立即执行函数来构造一个独立的作用域,

                                    2. 给这个作用域创立一个独立的变量来储存共有变量的值。

                                    首先我们用一个新的变量 j 来储存 i 的值,此时这个 j 就是独立于全局作用域的,仅仅在函数内部。之后,全局作用域把 i 的值传了过来,这样就能保证每个作用域的数据独立了。

                                    闭包的生命周期

                                    闭包的创建只有一个要求,内部函数使用了外部函数的数据,而且内部函数不需要调用,只需要执行定义就可以。所以一旦满足这个要求,闭包就会产生。

                                    另外要注意变量提升和方法提升。

                                     

                                     

                                    闭包的死亡其实很简单,只需要把外部函数的数据释放就可以了

                                     

                                     

                                     

                                    异常处理

                                    注意:JavaScript是解释型语言,所以一旦遇到异常之后,剩下的代码就不会执行了。

                                    异常类型

                                    ReferenceError

                                    引用错误,一般指引用的变量不存在

                                    1. 访问了不存在的变量

                                    1. 在初始化前访问变量

                                    注意,只有var声明的变量会进行变量提升,所以let和const声明的变量,必须先声明后使用。

                                    TypeError

                                    类型错误,指的是数据类型和使用的不一样

                                    1. 把普通变量当成函数

                                    RangeError

                                    范围错误,一般是指堆栈溢出

                                    1. 递归导致栈溢出

                                    SyntaxError

                                    语法错误

                                    try catch

                                    使用了捕获之后,程序就可以一直运行下去

                                    throw

                                    自定义异常

                                    继承Erroe类,可以用于自定义异常

                                    XML与JSON

                                    JSON

                                    JavaScript Object Notation(JavaScript对象表示法),简称JSON,其实就是一种更加规范的JavaScript对象。JSON的用途非常广泛,主要用于配置文件,还有数据的传递。

                                    实例

                                     

                                    字符串转JSON

                                     

                                    JSON转字符串

                                     

                                    对象转JSON

                                    遍历JSON对象

                                    如果find到了数据,会返回true

                                    访问本地JSON

                                     

                                    XML

                                    Extensible Markup Language

                                    获取XML文件并解析

                                    XML DOM

                                     

                                     

                                    正则表达式

                                    https://regex101.com/r/6aa35p/1/

                                    / /来打开正则表达式的范围。

                                    限定符

                                    限定符说明代码匹配结果
                                    ?前一个字符可有可无abc?ab 和 abc
                                    *前一个字符可以有多个或没有ab*cabbbc 和 abc 和 ac
                                    +前面的字符必须出现1次以上ab+cabc 和 abbbc
                                    {出现次数}前面字符出现的次数ab{2}cabbc
                                    {出现最小次数,最大次数}前面字符出现的次数范围ab{2,3}cabbc 和 abbbc
                                    {出现最小次数,}字符至少出现多少次,最高不限ab{2,}cabbc 和 abbbbc
                                    |或运算符a(b|c)ab 和ac
                                    [0-9]所有数字  
                                        

                                    元字符

                                    字符说明 
                                    \d所有数字 
                                    \w所有字母 
                                    \s空白符,包括tab 
                                    \D所有非数字 
                                    \W非单词字符 
                                    \S非空白符 
                                    .任意字符(不包括换行符) 
                                    ^a以a开头 
                                    b$以b结尾 

                                     

                                    Promise

                                    异步编程在程序开发中用的非常非常广泛,数据库访问、文件读写都需要用到。在ES5中,我们通常使用回调函数来解决,但是在ES6中,我们拥有了promise这种强大的工具来处理异步操作。

                                     

                                    为什么需要promise

                                    1. 可以解决回调地狱

                                      什么是回调地狱?简单来说,就是回调函数嵌套了好几层,可读性太差,不便于异常处理。

                                    2. 指定回调函数的方式更加灵活

                                    基本用法

                                     

                                     

                                     

                                     

                                     

                                     

                                    实例

                                    ajax封装

                                     

                                    延时调用

                                    下面是一个promise的实例,首先会生成一个随机数,如果该值小于0.5就会成功,如果大于0.5就会失败。

                                    promise成功时,用resolve来表示,失败时用reject来表示。这两个方法里面都可以传参数,这个参数会传递到then里面去。

                                    then有两个回调函数作为参数,第一个是resolve后调用,代表成功,第二个是reject后调用,代表发生错误。这个是系统自己调用的。

                                    换句话说,promise的构造函数里面写触发成功或失败的条件,而then里面写成功或失败之后的事件。

                                    文件读取

                                     

                                    我们把Promise里面的那个函数叫做执行器函数。执行器函数里面的代码是同步执行的

                                    util.promisify

                                    这个东西可以直接把一个异步的方法封装成promise对象。注意这个最好封装官方提供的异步方法。

                                     

                                     

                                     

                                    链式调用

                                     

                                    promise的属性

                                    PromiseState

                                    这个属性决定了promise在then的时候,到底是进入第一个回调函数,还是进入第二个。

                                    PromiseState有三个属性

                                    promise在创建的时候默认都是pending,如果调用了resolve或者reject之后,这个参数就会发生变化,而且一个promise对象这个属性只能改变一次

                                    在promise中throw一个异常,也会导致状态变为reject

                                    PromiseResult

                                    这个属性保存了你在promise时,传进来的参数。

                                     

                                     

                                    Peomise.resolve()

                                    这个方法比较特别,它的参数可以接收一个任意类型的数据,然后快速生成一个promise对象。

                                    之后它会返回一个promise对象,其PromiseState为fulfilled,PromiseResult为刚才传入的值。

                                     

                                    如果传入的参数为一个Promise对象,那么这个Promise对象触发了什么,就会传入什么属性。

                                    Peomise.resolve()

                                    可以快速创建一个失败的promise对象

                                    这个要注意,就算里面传入的对象是resolve,仍然会失败

                                    Promise.all()

                                    当全部的promise都resolve的时候

                                     

                                    如果有任何一个出现了reject,那么就会返回reject,并且result为第一个reject的值。

                                     

                                    多回调函数

                                    当PromiseState发生改变的时候,所有的then都会被执行;同样的,如果没有状态的改变。then里面的方法就不会执行。

                                    async和await

                                    基础

                                    如果对一个函数使用async修饰,那么就会返回一个promise对象,其值为函数的返回值,状态为fulfilled。

                                    如果该函数的返回值是一个promise对象,那么这个生成的promise对象将和返回的一样。

                                     

                                    await必须写在async函数中,但是async函数可以没有await。

                                    await一般修饰一个promise对象,如果promise对象的状态为成功,那么就会返回值。

                                    如果await修饰的promise状态为失败,那么会抛出一个异常,其参数为reject的值。

                                    用法

                                    如果有一堆异步的操作,但是又需要他们同步执行的时候,await就用到了。比如说我现在需要依次访问3个文件,并且打印他们的内容,如果单纯写3个readfile绝对会出大问题,因为异步操作并不确定哪个文件先被访问,但是如果对他们用await修饰的话,就能同步执行了。

                                     

                                     

                                     

                                     

                                    手写Promise

                                     

                                    题目

                                     

                                     

                                     

                                     

                                     

                                     

                                     

                                     

                                     

                                     

                                     

                                     

                                     

                                     

                                     

                                     

                                     

                                     

                                     

                                    请实现一个mergePromise函数,把传进去的数组按顺序先后执行,并且把返回的数据先后放到数组data中。

                                     

                                    网络请求

                                    HTTP

                                    HyperText Transfer Protocol(超文本传输协议)简称HTTP,

                                    HTTP的请求过程

                                    1. DNS解析,把域名解析成ip地址

                                    2. 建立TCP连接,三次握手

                                    3. 服务器接收数据,并处理,返回

                                    4. 客户端接收数据,进行页面渲染什么的

                                    请求报文

                                    由四部分组成,

                                    1. 请求行

                                    2. 请求头

                                    3. 空行

                                    4. 请求体

                                    下面就是一个http请求的一个例子

                                    请求头

                                    格式如下

                                    比如

                                    请求的方法为POST方法,访问的URL为/test.html,使用的协议版本为HTTP/1.1

                                    请求行

                                    请求体

                                     

                                     

                                    响应报文

                                    由四部分组成,

                                    1. 响应行

                                    2. 响应头

                                    3. 空行

                                    4. 响应体

                                    可以看到,响应体直接就是一个网页源码, 浏览器就是用这个来获取网页的。

                                    响应头

                                    格式如下

                                    响应行

                                    响应体

                                    URL的结构

                                     

                                    跨域

                                    Ajax

                                    Asynchronous JavaScript and XML

                                    用于前端页面向后台请求数据的

                                    基础结构

                                    //设置响应类型 xhr.responseType='json'

                                     

                                    GET/POST请求

                                     

                                    设置响应类型

                                     

                                    网络请求超时

                                    取消网络请求

                                    常用来防止请求重复发送

                                     

                                    Fetch

                                    GET

                                     

                                    POST

                                     

                                    http://httpbin.org/

                                    这个网站可以来模拟各种响应

                                    Axios

                                    安装

                                     

                                     

                                    事件循环(Event Loop)

                                     

                                    JavaScript中有调用栈(Call Stack)、微任务队列(Microtask Queue)和消息队列(Message Queue)组成,

                                    在js中,函数调用会压入栈,如果遇到异步操作如setTimeOut,会将里面的内容输入到消息队列,变成消息;而遇到Promise,则会压入到微任务队列。只有调用栈被清空,才能去执行微任务队列的内容;只有执行完了微任务,才会执行消息队列。这就是为什么setTimeOut的时间只是最小延迟。

                                     

                                    所以我们可以将JavaScript代码分为三种:

                                     

                                     

                                    js在事件循环的时候,首先会以此扫过代码,如果是同步任务就立刻执行,如果是微任务就放到微任务队列中,如果是宏任务就放到消息队列中。

                                    然后执行微任务队列。执行完毕之后,进行浏览器的渲染,进行数据更新。最后才进行宏任务的执行。

                                     

                                     

                                    循环与迭代

                                    label循环

                                    label不仅仅可以用于循环,也可以用于任何一个地方。只要是块就可以用,通过break来结束

                                    比如:

                                    代码运行到break test的时候,会跳转到test,然后把整个代码块都跳过

                                     

                                    在循环中,label可以发挥强大的力量

                                    本来break只能break里面那一层循环,但是一旦使用了label, 就会把整个循环全部跳出去

                                     

                                    同样的

                                     

                                     

                                    for……in(对象)

                                    这种语法可以遍历对象的

                                     

                                    如果对象是数组,那么就会返回数组的键。

                                     

                                    这是为什么?为什么for……in可以遍历对象的键?

                                     

                                    for……of(数组)

                                    首先来看看这个代码

                                    结果会直接输出a数组的元素。这看上去平淡无奇,但是我们对一个对象使用for……of会怎么样呢?

                                     

                                    一切的一切都是因为迭代器 (iterator)的缘故。

                                    for……of遍历的本质就是 使用数组内部的迭代器来遍历其数据,而普通的对象之所以无法使用for……of就是因为普通的对象没有迭代器。

                                    我们简单来看一下for……of的原理

                                    value是当前的值,done用来表示遍历是否结束。

                                     

                                    现在我们既然明白了原理,也不妨给对象也写一个类的迭代器。

                                    核心就是需要给对象设定一个Symbol.iterator属性。

                                     

                                    也可以使用生成器,

                                    可以看到,代码简单了不少,这就是语法糖的奥妙。

                                    迭代器

                                    首先来自制一个迭代器吧。

                                    生成器

                                    如果在函数名前面添加星号,就可以创建生成器。

                                    生成器里面会使用yield关键字。

                                    yield其实就是语法糖,引擎会自动帮你封装一个迭代器。只需要每次yield一个值即可。

                                    注意!yield只能在生成器内部使用!就算是生成器内部的函数也不行。

                                    生成器不需要返回值,如果写了的话,将会被视为最后一个值。

                                     

                                     

                                    forEach

                                    对象

                                     

                                    数组

                                     

                                     

                                    控制台调试

                                    JavaScript的控制台除了consolo.log()之外还有很多强大的功能。

                                    copy函数

                                     

                                    此时这个JSON对象就被复制到了你的复制粘贴缓存区中了,可以直接把这个JSON文本粘贴到任意地方,非常方便。

                                     

                                    当然了,除了对象之外,其他任何类型的数据都可以copy下来

                                     

                                     

                                    占位符与样式

                                     

                                    在ES6之后,可以通过模板字符串来代替一些占位符

                                     

                                    打印的方法

                                     

                                    console.log()

                                    最常用的方法,经常用于调试

                                    console.warn("hello world")

                                    打印一条警告信息

                                     

                                    console.info("hello world")

                                    console.error("hello world")

                                    console.dir()

                                    这个方法基本上和console.log()差不多,但是强大的地方在于它能够打印DOM元素的属性和方法。

                                     

                                    可以查看某个对象或者变量的具体信息,实际上在控制台直接输出这个对象也可以。

                                    这个方法基本上和console.log差不多,但是在打印DOM元素的时候,console.log会打印DOM元素本身,而console.dir()

                                    会打印其DOM对象

                                     

                                    image-20220408180634113

                                    可以看到第一个仅仅输出了DOM元素,而第二个会将详细信息显示出来。

                                     

                                    console.clear()

                                    清除控制台的信息

                                     

                                     

                                    console.assert()

                                     

                                    断言输出

                                    接收2个参数,第一个为断言条件,第二个为断言信息,只有当断言条件为false时,才会输出断言信息。

                                     

                                    客户端的console.assert()打印断言,并不会阻塞后续代码的执行,只是在断言的表达式为false的时候,向控制台打印你的内容。

                                    而在node.js中,值为假的断言将会导致一个AssertionError被抛出,使得代码执行被打断。这两者是有区别的。

                                     

                                     

                                    console.count()

                                    打印计数

                                    有时,我们需要统计一段代码的执行次数,那么就可以使用这个方法。

                                     

                                    如果不传参的话默认输出default

                                     

                                    console.time()

                                     

                                    计时

                                    这个方法可以跟踪并输出执行代码所需要的时间。

                                     

                                     

                                     

                                    console.group()

                                    只要把代码包在group里面,就可以对控制台的信息进行分组管理。

                                     

                                    console.table()

                                    表格输出

                                    把一个对象以表格的方式输出。

                                     

                                    console.trace()

                                    打印函数调用栈

                                    性能分析

                                    console.profile()

                                    image-20220408183027873

                                     

                                     

                                     

                                    命令菜单

                                    1. 按F12,打开控制台

                                    2. 按ctrl+shift+p,打开命令菜单

                                    命令  
                                    screenshot截图 
                                       
                                       

                                     

                                    点击一个元素,HTML右边会出现$0 的符号。然后在console中输入$0,可以打印这个DOM元素。

                                     

                                    强制刷新

                                    按住shift+F5

                                     

                                     

                                     

                                     

                                    canvas 2D

                                    canvas标签

                                    canvas是HTML5中新增的一个标签,只有widthheight两个属性。表示画布的宽度和高度。

                                    canvas的默认宽高是300*150px

                                    注意:不要用css来设置该标签的长宽,否则会失真!!!

                                    在设置宽高的时候请直接使用其属性来设置,否则canvas会出现拉伸的情况。

                                     

                                     

                                    再次强调!!!不要在css中设置canvas的宽高!否则无法正确获取

                                     

                                    图形绘制

                                    图形样式

                                     

                                    直线

                                    矩形

                                    圆弧

                                     

                                    概述

                                    基本结构

                                    获取canvas对象

                                    有了这个就相当于拥有了画布,我们才能在这上面作画。

                                    获取上下文环境

                                    这个就相当于画笔,拥有了这个我们才能进行绘画。

                                    除了'2d',之后还拥有3d的绘画机制。

                                    文字

                                    绘制文字

                                    字体样式

                                    文字阴影

                                     

                                    图形

                                    所有的图形都有描边和填充两种操作,分别用strokefill来表示

                                    填充与描边

                                    绘制图形

                                     

                                    路径

                                    圆弧 arc

                                    关于角度的问题,因为圆弧用的是弧度角所以一眼看不出来到底是多少度,所以推荐使用下面的写法

                                    圆弧 arcTo

                                    这个东西的方向是根据你moveTo的点所决定的。

                                     

                                    二次贝塞尔曲线

                                    三次贝塞尔曲线

                                    线条

                                    线条样式

                                    在我们使用stroke方法的时候,我们就绘制了线条,本章将会对线条的样式进行说明。

                                    颜色

                                    透明度

                                     

                                    线条粗细

                                    线条的取值为无单位的整数,默认值是1。

                                    线帽

                                    增加了线帽之后,线条的长度就会增加。该长度等于线宽,如果线宽为lineWidth,那么左右两边会分别增加lineWidth2的长度

                                    线条连接处

                                    当两个线条相交时,该样式可以设置样式相交点的表现

                                    join本身就有交会点的意思

                                    线条连接的长度

                                    只有1,2,3三个值

                                     

                                    图片

                                    基本格式

                                    三个重载

                                    制作视频

                                    drawImage方法不仅可以接收一个img对象,也可以接收一个video对象。

                                     

                                    动画

                                    坐标移动

                                    注意:这个方法会把整个坐标系都进行移动。

                                    坐标旋转

                                    注意:这个旋转会把整个坐标系都旋转。

                                    坐标缩放

                                     

                                    清空畫面

                                    canvas本身不能改变已经绘制的东西,所以若想实现动画的效果,就必须先清屏再重新绘制。

                                    save()把当前状态推入到绘图堆栈中,而restore()从绘图堆栈中的顶端弹出最近保存的状态,并且根据这些存储的值来设置当前绘图状态。

                                    画笔状态保存

                                    save和restore

                                     

                                    Path2D 对象

                                    这个对象功能非常强大,它可以来储存你之前所绘制的路径等信息。仅限于简单的路径。

                                    元素遮挡关系

                                     

                                     

                                    属性功能 
                                    copy只显示后来的image-20211115112827532
                                    source-over(默认)后来的图像覆盖之前的image-20211115112844982
                                    source-in后来的图像在上面,且只显示交集image-20211115112751453
                                    source-out只显示后来的图像image-20211115112214231
                                    source-atop后来的图像在上面image-20211115112321270
                                    destination-over image-20211115113005434
                                    destination-in image-20211115113030341
                                    destination-out image-20211115113053748
                                    destination-atop image-20211115113107386
                                    xor image-20211115113147967
                                    lighter  

                                    区域裁剪

                                    使用rect方法,划定一个区域;之后使用clip方法进行裁剪

                                    注意,这个裁剪并不是真的把画布裁剪掉。而是说,之后画布上的内容,只能在裁剪的区域内显示。也就是说这个区域裁剪实际上是说裁剪显示的区域。

                                     

                                    颜色渐变

                                    线性渐变

                                    线性渐变由两个部分组成,第一个是渐变的区域createLinearGradient,第二个是具体渐变的颜色addColorStop

                                    其中addColorStop取值为0到1。代表颜色占比。

                                     

                                     

                                    径向渐变

                                    代表从(74,75)这个圆,一直渐变到(90,60).第一个圆半径为5,第二个为100

                                     

                                    实例

                                    正多邊形

                                    这个玩意的原理实际上是先画一个圆,然后根据多边形的弧度角来计算具体坐标。

                                    比如说要画一个正三角形,那么首先算出弧度角

                                    2π3=120

                                    设角度从0开始,然后算出xy坐标,x=cos(degree),y=sin(degree)

                                    绘制调色板

                                    圆角矩形

                                    时钟

                                    对话框

                                     

                                     

                                     

                                     

                                     

                                    动画循环

                                     

                                    习题

                                     

                                     

                                     

                                    1. 一个作用域中,函数和变量重名,以函数优先

                                    2. 非匿名立即执行函数的函数名是只读的

                                    3. 在非严格模式下对函数名赋值会静默失败

                                     

                                     

                                     

                                     

                                    1. [0].valueOf() == [0]

                                    2. [0].toString() == "0"

                                    3. Number("0") == 0

                                    4. Number(true) == 1

                                     

                                     

                                     

                                     

                                    1. Promise.then()是微任务,但是then里面的代码是同步任务,所以会先执行,之后then会来处理执行的结果

                                    如果把then里面的参数改为函数,那么函数不会执行

                                     

                                     

                                     

                                    类型转换

                                    1. NaN和任何东西比较都是false,包括它自己

                                    2. null == undefined

                                    3. 对象会调用valueOf()方法取得原始值,再调用toString()方法变成字符串

                                    4. 字符串会调用parseInt()转为数值类型

                                    5. 布尔值,0是false,1是true

                                     

                                    面试题

                                    arguments相关

                                     

                                    在严格模式下,arguments和实参不会相互影响

                                    在非严格模式下,会影响

                                     

                                     

                                     

                                     

                                    作用域相关

                                     

                                     

                                    类型转换相关

                                     

                                    异步输出相关

                                     

                                     

                                     

                                    1.  

                                    数组输出

                                     

                                     

                                     

                                     

                                    JS数据类型有哪些?

                                    数值型:string、number、null、undefined、boolean

                                    引用型:object

                                     

                                     

                                    null和undefined的区别

                                     

                                    1. 作者在设计js的都是先设计的nu1l (为什么设计了null:最初设计js的时候借鉴了java的语言)

                                    2. null会被隐式转换成0,很不容易发现错误。

                                    3. 先有nu11后有undefined,undefined会转为NaN,出来undefined是为 了填补之前的坑。

                                    具体区别: JavaScript的最初版本是这样区分的: null是一个表示"无"的对象(空对象指针),转为数值时为0; undefined值,转为数值时为NaN。

                                     

                                    隐式转换题

                                    1. null==undefined

                                    =====的区别

                                    ==是比较值,而===除了值还会比较类型

                                    toString方法的理解

                                     

                                    对象的key是字符串类型

                                     

                                    题目

                                     

                                     

                                     

                                     

                                     

                                     

                                     

                                     

                                    async与defer

                                    img

                                     

                                     

                                     

                                    函数柯里化

                                    柯里化,英语:Currying,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

                                     

                                     

                                     

                                    现在要求,设计一个函数,使得下面的结果可以正常输出

                                     

                                     

                                    节流(throttle)

                                     

                                    防抖(debounce)

                                     

                                     

                                    手写promiese

                                     

                                     

                                    设计模式

                                    装饰器模式

                                    如果想为老的代码增加新的功能,那么不要直接去修改源码。

                                     

                                    这个模式在RPGMakerMV中很常见

                                    发布订阅者模式(观察者模式)

                                     

                                    定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得将得到通知

                                     

                                     

                                     

                                     

                                    重构

                                    多个if的重构

                                     

                                    策略模式

                                     

                                    惰性模式

                                    在dom绑定事件的时候,往往需要判断浏览器的兼容性

                                     

                                    自执行实现

                                    函数重写实现

                                     

                                     

                                    附录1——常用代码

                                    禁用鼠标右键菜单

                                     

                                    把伪数组转真数组

                                     

                                    随机数

                                     

                                     

                                     

                                    数组去重

                                     

                                     

                                    五星制评分打印

                                     

                                    给对象添加方法

                                     

                                    多维数组拆成一维

                                     

                                    块作用域的妙用

                                     

                                    变量交换

                                     

                                    ###

                                     

                                     

                                    数组浅拷贝

                                     

                                     

                                    取整

                                    金钱格式化

                                    JSON深拷贝

                                    最大值与最小值

                                    如何使a同时等于三个数

                                    图片懒加载

                                     

                                    一元运算符+转数字

                                     

                                    ES5默认参数

                                     

                                     

                                    数字的位数

                                    对象的比较

                                    如果有两个对象,

                                     

                                    弹窗

                                     

                                     

                                     

                                     

                                    附录2——JavaScript对象方法速查

                                    Array

                                     

                                     

                                     

                                     

                                    排序

                                     

                                     

                                    every

                                    每一项都要符合

                                    some

                                    flat [ES10]

                                    扁平化数组

                                    flatMap [ES10]

                                    flat是把一个多维数组扁平化,而flatMap 是把对数组处理的结果扁平化

                                    如果想要对结果扁平化的话

                                     

                                    Object

                                    Object.keys()

                                     

                                    Date

                                    String

                                     

                                    附录3——js库推荐