JS学习

计算机基础

1.编程语言

**编程:**就是让计算机为解决某个问题而使用某种程序设置语言编写程序代码,并最终得到结果的过程

**计算机程序:**就是计算机所执行的一系列的指令集合

2.计算机基础

计算机由硬件软件组成

硬件可分为**输入设备、输出设备、CPU、硬盘、内存**

软件可分为**系统软件、应用软件**

JavaScript初识

完整的JavaScript实现包含以下几个部分:

  • 核心(ECMAScript)
  • 文档对象模型(DOM)
  • 浏览器对象模式(BOM)

JavaScript的作用

  • 表单动态校验(密码强度检测)【JS产生最初的目的
  • 网页特效
  • 服务端开发(Node.js)
  • 桌面程序(Electron)
  • App(Cordova)
  • 控制硬件-物联网(Ruff)
  • 游戏开发(cocos2d-js)

标识符

在JS中所有的可以由我们自主命名的都可以称为标识符

例如:变量名、函数名、属性名都属于标识符

命名一个标识符时需要遵守如下的规则:

  1. 标识符中可以含有**字母 数字 _  $**
  2. 标识符==不能==以数字开头
  3. 标识符不能是ES中的关键字或保留字
  4. 标识符一般采用驼峰命名法

<script>元素

<script>有下列8个属性

  • **async:可选。表示应该立即开始下载脚本,但不能阻止其他页面动作,比如下载资源或等待其他脚本加载。只对**外部脚本文件有效
  • **charset**:可选。使用src属性指定的代码字符集。这个属性很少使用,因为大多数浏览器不在乎它的值
  • **crossorigin**:可选。配置相关请求的CORS(跨源资源共享)设置。默认是不使用CORScrossorigin="anonymous"配置文件请求不必设置凭据标志。crossorigin="use-credentials"设置凭据标志,意味着出站请求会包含凭据
  • **defer**:可选。表示脚本可以延迟到文档完全被解析和显示之后再执行。只对外部脚本文件有效。在IE7及更早的版本中,对行内脚本可以指定这个属性
  • **integrity**:可选。允许对比接收到的资源和指定的教秘签名以验证子资源完整型。如果接收到的资源的签名与这个属性指定的签名不匹配,则页面会报错,脚本不会执行。这个属性可以用于确保内容分发网络不会提供恶意内容
  • **language**:废弃
  • **src**:可选。表示包含要执行的代码的外部文件
  • **type**:可选。代替language,表示代码中脚本语言的内容类型

在使用行内JavaScript代码时,要注意代码中不能出现字符串</script>

数据类型

数据类型指的就是==字面量==的类型

一共有七种数据类型:

  • Null
  • Undefined
  • Number
  • String
  • Boolean
  • Symbol
  • Object【Array Function】

判断数据类型的方法

  • typeof【返回数据类型的==字符串==表达】
    • 可以判断:undefined/数值/字符串/布尔值
    • 不能判断:null/object
  • instanceof 【返回true或者false】
    • 专门用于判断对象的具体类型
  • ===
    • 可以判断:undefined/null

常见问题

  1. undefined与null的区别?

    **回答:**undefined代表定义未赋值,null代表定义并赋值了,但是值为null

  2. 什么时候给变量赋值为null呢?

    **回答:**初始赋值为null,代表将要赋值为对象

    ​ 结束前,让对象成为垃圾对象(被垃圾回收期回收)

  3. 严格区别变量类型与数据类型?

    回答:

               1. 数据的类型
            + 基本类型
            + 对象类型
               2. 变量的类型(变量内存值的类型)
         + 基本类型:保存就是基本类型的的值
         + 引用类型:保存的是地址值
    
  4. null与{}的区别

    **回答:**null是一个不存在的对象占位符。{}是一个真正的对象,只不过其中没有数据而已。

数据-变量-内存

  1. 什么是数据?
    • 在内存中可读的, 可传递的保存了特定信息的东西
    • 一切皆数据, 函数也是数据
    • 在内存中的所有操作的目标: 数据
  2. 什么是变量?
    • 在程序运行过程中它的值是允许改变的量
    • 一个变量对应一块小内存, 它的值保存在此内存中
  3. 什么是内存?
    • 内存条通电后产生的存储空间(临时的)
    • 一块内存包含2个方面的数据
      • 内部存储的数据
      • 地址值数据
    • 内存空间的分类
      • 栈空间: 全局变量和局部变量
      • 堆空间: 对象
  4. 内存,数据, 变量三者之间的关系
    • 内存是容器, 用来存储不同数据
    • 变量是内存的标识, 通过变量我们可以操作(读/写)内存中的数据

数值转换

有三个函数可以将非数值转换为数值:**Number()、parseInt()和parseFloat()Number()**是转型函数,可用于任何数值类型。后两个函数主要用于将字符串转换为数值。对于同样的参数,这三个函数执行的操作也不同。

**Number()**函数基于如下规则执行转换。

  • 布尔值,true转换为1,false转换为0
  • 数值,直接返回
  • null,返回0
  • undefined,返回NaN
  • 字符串,应用以下规则:
    • 如果字符串包含数值字符,包括数值字符前面带加、减号的情况,则转换为一个十进制数值。因此,Number("1")返回1,Number("011")返回11(忽略前面的零)
    • 如果字符串包含有效的浮点值格式如”1.1”,则会转换为响应的浮点值(同样,忽略前面的零)
    • 如果字符串包含有效的十六进制格式如”0xf”,则会转换为该十六进制对应的十进制整数
    • 如果是空字符串(不包含字符),则返回0
    • 如果字符串包含除上述情况之外的其他字符,则返回NaN
  • 对象,调用valueOf()方法,并按照上述规则则转换返回的值。如果转换结果是NaN,则调用toString()方法,再按照转换字符串的规则转换

考虑到用Number()函数转换字符串时相对复杂且有点反常规,通常在需要得到整数时可以优先使用parseInt()函数。parseInt()函数更专注于字符串是否包含数值模式。字符串最前面的空格会被忽略,从第一个非空格字符开始转换。如果**第一个字符不是数值字符、加号或减号parseInt()立即返回NaN。这意味着空字符串也会返回NaN(这一点和Number()不一样,它返回0).如果第一个字符时数值字符、加号或减号**,则继续依次检测每个字符,直到字符串末尾,或碰到非数值字符。例如parseInt("1234blue")返回1234,parseFloat("22.5")返回22

同时,parseInt()也接收第二个参数,用于指定底数(进制数)

parseFloat()函数的工作方式跟parseInt()类似,都是从位置0开始检测每个字符。parseInt("1234blue")返回1234,parseFloat("22.5")返回22.5,parseInt("22.34.5")返回22.34,

对象

  1. 什么是对象?

    • 多个数据(属性)的集合
    • 用来保存多个数据(属性)的容器
  2. 为什么要用对象

    **回答:**统一管理多个数据

  3. 对象的组成

    • 属性 【属性名(字符串)+属性值(任意类型)组成】
    • 方法:一种特别的属性【属性值是函数】
  4. 如何访问对象内部数据?

    • 对象.属性名===类似碰到”school-name”这种无法取出
    • 对象[“属性名”]===全能选手
  5. 什么情况下必须使用[‘属性名’]的方式?

    • 属性名包含特殊字符:- 空格

    • 变量名不确定

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      p={
      name: '张三',
      school_name: '宇宙大学',
      }
      let propName='myAge';
      let value=18;
      //p.propName=value【该情况不能使用 对象.属性名 会出现
      //{name: '张三', school_name: '宇宙大学', propName: 18}】
      p[propName]=value
      //{name: '张三', school_name: '宇宙大学', myAge: 18}

Date对象

在JS中使用Date对象来表示一个时间

创建一个Date对象

创建当前时间对象

1
let d=new Date()

如果直接使用构造函数创建一个Date对象,则会封装为当前代码执行的时间

1
Sat Dec 04 2021 19:04:28 GMT+0800 (中国标准时间)

创建一个指定的时间对象

需要在构造函数中传递一个表示时间的字符串作为参数

1
2
let d2=new Date("12/03/2016 11:10:30")
//Sat Dec 03 2016 11:10:30 GMT+0800 (中国标准时间)

日期格式:月份/日/年 时:分:秒

Date对象的方法

getDate()

获取日期对象是几日

1
2
3
let d2=new Date("12/03/2016 11:10:30")
console.log(d2.getDate());
//3

getDay()

表示日期对象是周几

会返回一个==0~6==的值【0表示周天,1表示周一…】

1
2
3
let d2=new Date("12/03/2016 11:10:30")
console.log(d2.getDay());
//6

getMonth()

表示是几月

会返回一个==0~11==的值【0表示1月,1表示2月…】

1
2
3
let d2=new Date("12/03/2016 11:10:30")
console.log(d2.getMonth());
//11

getFullYear()

表示是哪一年

1
2
3
let d2=new Date("12/03/2016 11:10:30")
console.log(d2.getFullYear());
//2016

getTime()

获取日期对象时间戳【时间戳,指的是从格林威治标准时间的1970年1月1日 0时0分0秒 到日期对象所花费的毫秒数】

1
2
3
let d2=new Date("12/03/2016 11:10:30")
console.log(d2.getTime());
//1480734630000

JS数组

  • 能够知道为什么要有数组
  • 能够创建数组
  • 能够获取数组中的元素
  • 能够对数组进行遍历
  • 能够给数组新增一个元素
  • 能够独立完成冒泡排序的案例

1.数组的概念

什么是数组?

数组是指一组数据的集合,其中的每个数据被称作元素,在数组中可以存放任意类型的元素。数组是一种将一组数据存储在单个变量名下的优雅方式

2.创建数组

JS中创建数组有两种方式:

  • 利用new创建数组
  • 利用数组字面量创建数组
1
2
let arr=new Array();
let arr2=[];

3.数组方法

1.push

该方法可以向数组末尾添加一个或多个元素,并返回数组新的长度

2.pop

该方法可以删除数组的最后一个元素,并将删除的元素作为返回值返回

3.unshift

该方法可以向数组开头添加一个或多个元素,并返回数组新的长度

4.shift

该方法可以删除数组的开头第一个元素,并将删除的元素作为返回值返回

5.slice

该方法可从已有的数组中返回选定的元素,该方法不会改变元素数组,而是将截取到的元素封装到一个新数组中返回

参数:

  1. 截取开始位置的索引,包含开始索引
  2. 截取结束的位置的索引,不包含结束索引
    1. 第二个参数可以省略不写,此时会截取从开始索引位置往后所有元素

索引可以传递负值

6.splice

用于删除数组中的指定元素

使用splice()会影响到原数组,会将指定元素从原数组中删除

参数:

  1. 表示开始位置的索引
  2. 表示删除的数量
  3. 可以插入一些新的数据到开始位置的索引

正则表达式

创建正则表达式的方式

  1. var 变量 =new RegExp(“正则表达式”,”匹配模式”);

  2. 使用字面量来创建的形式

    1
    var 变量=/正则表达式/匹配模式

    PS:不要写引号

匹配模式

修饰符 描述
i 不区分大小写
g 执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)。
m 执行多行匹配。【可用于单规则多行去匹配】
u Unicode

m代表的是多行匹配

原子表和原子组

原子表

原子表[]中的内容本身具有或者的意思

原子组

字符

元字符

字符 描述
x|y 匹配x或y。例如,“`z
[xyz] 字符集合。匹配所包含的任意一个字符。例如,“[abc]”可以匹配“plain”中的“a”。
[^xyz] 负值字符集合。匹配未包含的任意字符。例如,“[^abc]”可以匹配“plain”中的“p”。
[a-z] 字符范围。匹配指定范围内的任意字符。例如,“[a-z]”可以匹配“a”到“z”范围内的任意小写字母字符。
[^a-z] 负值字符范围。匹配任何不在指定范围内的任意字符。例如,“[^a-z]”可以匹配任何不在“a”到“z”范围内的任意字符。
\b 匹配一个单词边界,也就是指单词和空格间的位置。例如,“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”。
\B 匹配非单词边界。“er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er”。
\cx 匹配由x指明的控制字符。例如,\cM匹配一个Control-M或回车符。x的值必须为A-Z或a-z之一。否则,将c视为一个原义的“c”字符。
\d 匹配一个数字字符。等价于[0-9]。
\D 匹配一个非数字字符。等价于[^0-9]。
\f 匹配一个换页符。等价于\x0c和\cL。
\n 匹配一个换行符。等价于\x0a和\cJ。
\r 匹配一个回车符。等价于\x0d和\cM。
\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。
\S 匹配任何非空白字符。等价于[^ \f\n\r\t\v]。
\t 匹配一个制表符。等价于\x09和\cI。
\v 匹配一个垂直制表符。等价于\x0b和\cK。
\w 匹配包括下划线的任何单词字符。等价于“[A-Za-z0-9_]”。【只包括字母数字下划线,不包括其它特殊字符】
\W 匹配任何非单词字符。等价于“[^A-Za-z0-9_]”。

特殊字符

特别字符 描述
$ 匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 $ 也匹配 ‘\n’ 或 ‘\r’。要匹配 $ 字符本身,请使用 $。
( ) 标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用。要匹配这些字符,请使用 ( 和 )。
* 匹配前面的子表达式零次或多次。要匹配 * 字符,请使用 *。
+ 匹配前面的子表达式一次或多次。要匹配 + 字符,请使用 +。
. 匹配除换行符 \n 之外的任何单字符。要匹配 . ,请使用 . 。
[ 标记一个中括号表达式的开始。要匹配 [,请使用 [。
? 匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。要匹配 ? 字符,请使用 ?。
\ 将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, ‘n’ 匹配字符 ‘n’。’\n’ 匹配换行符。序列 ‘\‘ 匹配 “",而 ‘(‘ 则匹配 “(“。
^ 匹配输入字符串的开始位置,除非在==方括号表达式==中使用,当该符号在方括号表达式中使用时,表示不接受该方括号表达式中的字符集合。【例如 [^a]代表不是字符a】要匹配 ^ 字符本身,请使用 ^。
{ 标记限定符表达式的开始。要匹配 {,请使用 {。
| 指明两项之间的一个选择。要匹配 |,请使用 |。

限定符

字符 描述
* 匹配前面的子表达式零次或多次。例如,zo* 能匹配 “z” 以及 “zoo”。* 等价于{0,}。
+ 匹配前面的子表达式一次或多次。例如,’zo+’ 能匹配 “zo” 以及 “zoo”,但不能匹配 “z”。+ 等价于 {1,}。
? 匹配前面的子表达式零次或一次。例如,”do(es)?” 可以匹配 “do” 、 “does” 中的 “does” 、 “doxy” 中的 “do” 。? 等价于 {0,1}。
{n} n 是一个非负整数。匹配确定的 n 次。例如,’o{2}’ 不能匹配 “Bob” 中的 ‘o’,但是能匹配 “food” 中的两个 o。
{n,} n 是一个非负整数。至少匹配n 次。例如,’o{2,}’ 不能匹配 “Bob” 中的 ‘o’,但能匹配 “foooood” 中的所有 o。’o{1,}’ 等价于 ‘o+’。’o{0,}’ 则等价于 ‘o*’。
{n,m} m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,”o{1,3}” 将匹配 “fooooood” 中的前三个 o。’o{0,1}’ 等价于 ‘o?’。请注意在逗号和两个数之间不能有空格。【{7,9}代表的是7~9个,但是超过9位,还是能匹配上】

断言匹配

字符 描述
?= 询问后面跟着的东西是这个【?=a 后面跟着的是a的】
?! 询问后面跟着的东西不是这个【?!a 后面跟着的不是a的】
?<! 前面跟着的不是这个值的【?<!a 后面跟着的不是a的】
?<= 前面跟着的是这个值的【?<=a 后面跟着的是a的】

PS:不一定是最前面或者最后面开始

正则表达式的方法

test()

语法

1
2
3
var reg =new RegExp("正则表达式","匹配模式");
var str="a";
reg.test(str)

使用这个方法用来检查一个字符串是否复合正则表达式的规则

如果复合则返回true,否则就返回false

match()

匹配复合规则的数据,用数组储存

1
2
3
const reg=/正则表达式/匹配模式;
const str="adsd";
str.match(reg)

exec()

获取复合规则的每一项的值

this的指向

window的函数叫函数,对象的函数叫方法

  1. this是什么?
    • 任意函数本质上都是通过某个对象调用的,如果没有明确指定,那就是window
    • 所有函数内部都有一个变量this
    • 它的值是调用函数的当前对象
  2. 如何确定this的值?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Person(color){
console.log(this);
this.getColor=function(){
console.log(this);
return this.color;
};
this.setColor=function(color) {
console.log(this)
this.color=color;
}
}

Person("red");//this是window

var p=new Person("yellow");//this是p;

p.getColor();//this是p

var obj={}
p.setColor.call(obj,"black"); //this是obj

var test=p.setCOlor;
test(); //this是window

总结

  1. 以函数形式调用时,this永远都是window
  2. 以方法的形式调用时,this时调用方法的对象
  3. 以构造函数的形式调用时,this是新创建的那个对象
  4. 使用call和apply调用时,this是指定的那个对象
  5. 在寻找箭头函数得this指向时,就看箭头函数外层是否有函数: 如果有,那么外层函数的this就是内部箭头函数的this; 如果没有,那么this就是window。

JS函数

  1. 什么是函数?

    • 实现特定功能的n条语句的封装体
    • 只有函数是可以执行的,其他类型的数据不能执行
  2. 为什么要用函数?

    • 提高代码复用性
    • 便于阅读交流
  3. 如何定义函数?

    • 函数声明

      1
      fuction fn(){}
    • 表达式

      1
      var fn2=function(){}

      **区别:**函数可以在函数声明之前调用,而函数表达式的函数只能在声明之后调用函数声明整体会被提升到当前作用域的顶部,函数表达式也提升到顶部但是只有其变量名提升)

  4. 如何调用(执行)函数?

    • 直接调用

      1
      fn();
    • 通过对象调用

      1
      obj.fn()
    • new调用

      1
      new fn()
    • call/apply调用

      1
      2
      3
      4
      5
      6
      7
      8
      fn.call/apply(obj)//===obj.test
      //临时让test成为obj的方法进行调用【可以让任意函数成为任意对象的方法进行调用】
      var obj={}
      funtcion test2(){
      this.xxx="dididi"
      }
      //obj.test2() 不能直接调用,obj里没有该方法
      test2.call(obj);//这样就可以

call()和apply()

  • 这两个方法都是函数对象的方法,需要通过函数对象来调用
  • 当对函数调用call()和apply()都会调用函数执行
  • 在调用call和apply(),可以将一个对象对象指定为第一个参数
    • 此时这个对象将会成为函数执行时的this
  • call()方法可以将实参在对象之后依次传递
  • apply()方法需要将实参封装到一个数组中统一传递

arguments的使用

在调用函数时,浏览器每次都会传递进两个隐含的参数

  1. 函数的上下文对象this
  2. 封装实参的对象 arguments
    1. arguments是一个类数组对象,它也可以通过索引来操作数据,也可以获取类型
    2. 在调用函数时,我们所传递的实参都会在arguments中保存
    3. 我们即使不定义形参,也可以通过arguments来定义实参,只不过比较麻烦
  3. 它里面有一个属性叫做callee,这个属性对应一个函数对象,就是当前正在指向的函数的对象

当我们不确定有多少个参数传递的时候,可以用**arguments来获取。在JavaScript中,arguments实际上它是当前函数的一个内置对象**。所有函数都内置了一个arguments对象,arguments对象中存储了传递的所有实参

arguments展示形式是一个伪数组,因此可以进行遍历。伪数组具有以下特点:

  • 具有length属性
  • 按索引方式储存数据
  • 不具有数组的push,pop等方法

构造函数

构造函数与普通函数的区别

  1. 构造函数通常函数名首字母大写,普通函数不需要
  2. 调用方式不同。构造函数使用new关键字创建,普通函数是直接调用
  3. 若函数中无返回值返回,打印构造函数创建的对象显示是object,普通函数赋值的则为undefined

构造函数的执行流程:

  1. 立即创建一个新的**对象**
  2. 将新建的对象设置为函数中this,在构造函数中可以使用this来引用新建的对象重要
  3. 逐行执行函数中的代码
  4. 将新建的对象作为返回值返回

回调函数

  1. 什么函数才是回调函数?
    • 你定义的,你没有调,但是它最终执行了
  2. 常见的回调函数?
    • dom事件回调函数
    • 定时器回调函数
    • ajax请求回调函数
    • 声明周期回调函数

**PS:**回调函数是异步的

IIFE

**全称:**Immediately-Invoked Function Expression【立即调用函数表达式】

这是一个被称为==自执行匿名函数==的设计模式/l,主要包含两部分:

  1. 第一部分是包围在==圆括号运算符()==里的一个匿名函数,这个匿名函数拥有独立的词法作用域。这不仅避免了外界访问此 IIFE 中的变量,而且又不会污染全局作用域。
  2. 第二部分再一次使用 () 创建了一个立即执行函数表达式,JavaScript 引擎到此将直接执行函数。

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
(function () {
var name = "Barry";
})();
// 无法从外部访问变量 name
name // 抛出错误:"Uncaught ReferenceError: name is not defined"


var result = (function () {
var name = "Barry";
return name;
})();
// IIFE 执行后返回的结果:
result; // "Barry"

PS:当函数变成立即执行的函数表达式时,表达式中的变量不能从外部访问。

作用:

  • 隐藏实现
  • 不会污染外部命名空间
  • 用它来编写js模块

原型

  1. 函数的prototye属性

    • 我们所创建的每一个函数都有一个prototype属性,它默认指向一个Object空对象(即称:原型对象)

      • 如果函数作为普通函数调用,prototype没有任何作用
      • 当函数通过构造函数调用时,它所创建的对象中都有一个隐含的属性,指向该构造函数的原型对象,我们可以使用__proto__来访问该属性
    • 原型对象中有一个属性constructor,它指向函数对象【Date.prototype.constructor===Date】

    • 原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象,我们可以将对象中共有的内容,统一设置到原型对象中

      image-20211205163614556

  2. 给原型函数添加属性(一般是方法)

    **作用:**函数的所有实例对象自动拥有原型中的属性(方法)【原型上的方法就是给实例对象使用的】

显式原型与隐式原型

image-20211205180630883

1
2
3
4
5
function Fn() {

}
console.log(Fn.prototype);
console.log(Fn.__proto__)
  • 所有**函数都有一个特别的属性**:

    • prototype : 显式原型属性【在定义函数时自动添加的,默认时一个空Object对象】
  • 所有**实例对象都有一个特别的属性**:

    • __proto__ : 隐式原型属性【创建对象时自动添加的,默认值为构造函数的prototype属性值】
  • 对象的隐式原型的值为其对应构造函数的显示原型的值

    1
    2
    3
    function Fn{};
    console.log(Fn.prototype===Fn.__proto__)
    //true
  • 显式原型与隐式原型的关系

    • 函数的prototype: 定义函数时被自动赋值, 值默认为{}, 即用为原型对象
    • 实例对象的__proto__: 在创建实例对象时被自动添加, 并赋值为构造函数的prototype值
    • 原型对象即为当前实例对象的父对象
  • 能直接操作显式原型,但不能直接操作隐式原型(ES6之前)

原型链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Fn(){
this.test1=function(){
console.log("test1()")
}
}

Fn.prototype.test2=function(){
console.log("test2()")
}

var fn=new Fn();
fn.test1();
fn.test2();
fn.toString();
fn.test3();
  1. 访问一个对象的属性时

    • 先在自身属性中查找,找到返回
    • 如果没有,再沿着__proto__这条链向上查找,找到返回
    • 如果最终没找到,返回undefined
  2. 别名:隐式原型链

  3. 作用:查找对象的属性(方法)

  4. 原型链

    • 所有的实例对象都有__proto__属性, 它指向的就是原型对象
    • 这样通过__proto__属性就形成了一个链的结构—->原型链
    • 当查找对象内部的属性/方法时, js引擎自动沿着这个原型链查找
    • 当给对象属性赋值时不会使用原型链, 而只是在当前对象中进行操作
  5. 其实函数上面既有隐式原型,也有显式原型【所有函数都有一个隐式原型,指向Function的显式原型】

    1
    2
    function Foo{}
    var Foo=new Function()//实例对象上面有隐式原型
  6. 所有函数的__proto__都是一样的【都是通过new Function产生的】

  7. 使用in检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true

    1
    console.log("xxx" in person)
  8. 可以使用对象的hasOwnProperty()来检查对象自身中是否含有某属性

    1
    person.hasOwnProperty("xxx")

instanceof

  1. instanceof是如何判断的?
    • 表达式:A instanceof B
    • 如果B函数的显式原型对象在A对象的原型链上,返回true,否则返回false
  2. Function是通过new自己产生的实例

面试题

image-20211207140143201
  1. ```js
    function A(){}
    A.prototype.n=1;
    var b=new A()
    A.prototype={
    n:2,
    m:3
    }
    var c=new A();
    console.log(b.n,b.m,c.n,c.m);
    //1,undifined,2,3
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    2. ```js
    function F(){}
    Object.prototype.a=function(){
    console.log('a()')
    }
    Function.prototype.b=function(){
    console.log('b()')
    }
    var f=new F()
    f.a() //a()
    f.b() //f.b is not a function【b是往Function上的原型对象添加的,object无法访问到】
    F.a() //a()
    F.b() //b()

垃圾回收(GC)

  • 就像人生活的时间长了会产生垃圾一样,程序运行过程中也会产生垃圾
  • 当一个对象没有任何的变量或属性对它进行引用,此时我们将永远无法操作该对象,该种对象过多会导致程序运行变慢,需要进行垃圾清理
  • 在JS中拥有自动的垃圾回收机制,会自动将这种垃圾对象从内存种销毁。我们不需要也不能进行垃圾回收的操作
  • 我们需要做的是将不再使用的对象设置为null即可

变量提升与函数提升

变量提升

  • 通过var定义(声明)的变量,在定义语句之前就可以访问到
  • 值:undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
var a=3
function fn(){
console.log(a)
var a=4
}
fn();
//undefined
//相当于
//function fn(){
// var a;//函数内变量提升到第一行
// console.log(a)
// a=4
//}

函数声明提升

  • 通过function声明的函数,在之前就可以直接调用
  • 值:函数定义(对象)
1
2
3
4
5
6
7
8
fn2() //可调用 函数声明式提升
fn3 //不能 变量提升
function fn2)(){
console,log('fn2()')
}
var fn3=function () {
console.log('fn3()')
}

执行上下文

  1. 代码分类
    • 全局代码
    • 函数(局部)代码
  2. 全局执行上下文
    1. 在执行全局代码前将window确定为全局执行上下文(虚拟的,存在于栈中)
    2. 对全局数据进行预处理
      • var定义的全局变量==>undefined,添加为window的属性
      • function声明的全局变量==>赋值(fun),添加为window的方法
      • this==》赋值(window)
    3. 开始执行全局代码
  3. 函数执行上下文
    1. 在调用函数,准备执行函数体之前,创建对应的函数执行上下文
    2. 对局部数据进行预处理
      • 形参变量==>赋值(实参)==>添加为执行上下文的属性
      • arguments==>赋值(实参列表)==>添加为执行上下文的属性
      • var定义的局部变量==>undefined==>添加为执行上下文的属性
      • function声明的函数==>赋值(fun)==>添加为执行上下文的方法
      • this==>赋值(调用函数的对象)
    3. 开始执行函数体代码

执行上下文栈

  1. 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
  2. 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
  3. 在函数执行上下文创建后, 将其添加到栈中(压栈)
  4. 在当前函数执行完后,将栈顶的对象移除(出栈)
  5. 当所有的代码执行完后, 栈中只剩下window

作用域与作用域链

  1. 理解
    • 就是一块“地盘”,一个代码段所在的区域
    • 它是静态的(相对于上下文对象),在编写代码时就确定了
  2. 分类
    • 全局作用域
    • 函数作用域
    • 块作用域
  3. 作用
    • 隔离变量,不同作用域下面同名变量不会有冲突
  4. 定义几个作用域=定义几个函数+1

作用域说明

全局作用域
  1. 全局作用域局势页面打开时被创建,页面关闭时被销毁
  2. 编写在script标签中的变量和函数,作用域为全局,在页面的任意位置都可以访问到
  3. 在全局作用域中有全局对象window,代表一个浏览器窗口,由浏览器创建,可以直接调用
  4. 全局作用域中声明的变量和函数会作为window对象的属性和方法保存
函数作用域
  1. 在调用函数时,函数作用域创建,函数执行完毕,函数作用域被销毁
  2. 每调用一次函数就会创建一个新的函数作用域,他们之间是相互独立的
  3. 在函数作用域中可以访问到全局作用域的变量,在函数外无法访问到函数作用域内的变量
  4. 在函数作用域中访问变量、函数时,会现在自身作用域中寻找,若没有找到,则会到函数上一级作用域中寻找,一直到全局作用域

深层次理解

  • 执行期的上下文

    • 当函数代码执行的前期,会创建一个执行上下文的的内部对象 AO(作用域)

    • 这个内部的对象是预编译的时候创建出来的 因为当函数被调用的时候 会先进行预编译

    • 在全局代码执行的前期会创建一个执行期的上下文对象GO

      • 函数的作用域预编译

        1. 创建AO对象,AO{}
        2. 找形参和变量声明 将变量和形参名 当作AO对象的属性名,值为undefined
        3. 实参形参相统一
        4. 在函数体里面找函数声明 值赋予函数体
      • 全局作用域的预编译

        1. 创建GO对象
        2. 找变量声明 将变量 当作AO对象的属性名,值为undefined
        3. 找函数声明,值赋给函数体

闭包

  1. 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包
  2. 闭包到底是什么?
    • 理解一:闭包时嵌套的内部函数(绝大部分人)
    • 理解二:包含被引用变量(函数)的对象(极少数人)
  3. 产生闭包的条件?
    • 函数嵌套
    • 内部函数引用了外部函数的数据(变量/函数)

DOM

  • 全称Document Object Model【文档对象模型】,DOM是一个应用编程接口(API),用于在HTML中使用扩展的XML
  • JS通过DOM来对HTML文档进行操作。只要理解了DOM就可以随心所欲的操作WEB页面
  • 文档
    • 文档表示的就是整个的HTML网页文档
  • 对象
    • 对象表示将网页中的每一个部分都转换为了一个对象
  • 模型
    • 使用模型来表示对象之间的关系,这样方便我们获取对象

1998年10月,DOM Level1称为W3C的推荐标准。这个规范由两个模块组成:**DOM CoreDOM HTML**。前者提供了一种映射XML方法,从而方便访问和操作文档任意部分的方式;后者拓展了前者,并增加了特定于HTML的对象和方法。

节点

  • 节点Node,是构成我们网页的最基本的组成部分,网页中的每一个部分都可以称为是一个节点
  • 比如:html标签、属性、文本、注释、整个文档等都是一个节点
  • 虽然都是节点,但是实际上他们的具体类型是不同的
  • 比如:标签问我们称为元素节点,属性称为属性节点,文本称为文本节点,文档称为文档节点
  • 节点的类型不同,属性和方法也不尽相同
  • 如果需要读取元素节点属性:
    • 直接使用元素.属性名
      • 例子:元素.id 元素.name 元素.value
      • 注意:class属性不能采用这种方式,读取class属性是需要使用 元素.className

节点的属性

nodename NODEtYPE NODEVALUE
文档节点 #document 9 null
元素节点 标签名 1 null
属性节点 属性名 2 属性值
文本节点 #text 3 文本内容

DOM增删改查

DOM查询

获取元素节点【通过document对象调用】

  1. getElementById()
    1. 通过**id属性获取一个**元素节点对象
  2. getElmentsByTagName()
    1. 通过**标签名获取一组**元素节点对象
  3. getElementsByName()
    1. 通过**name属性获取一组**元素节点对象
  4. querySelector()
    1. 需要一个选择器的字符串作为参数,可以根据一个CSS选择器来查询一个元素节点对象
  5. querySelectorAll()
    1. 该方法和querySelector()用法类似,不同的是它会将符合条件的元素装到一个数组中返回
    2. 即使符合条件的元素只有一个,它也会填充进数组中

获取元素节点的子节点【通过具体的元素节点调用】

  1. getElementsByTagName()
    1. **方法**,返回当前节点的指定元素名后代节点
  2. childNodes
    1. 属性,表示当前节点的所有子节点
    2. **注意**:在IE8及以下的浏览器中,不会将空白文本当作子节点,所以该属性在IE8中会返回4个子元素,而其他浏览器是9个
  3. firstChild
    1. **属性**,表示当前节点的第一个子节点
  4. lastChild
    1. 属性,表示当前节点的最后一个子节点

获取父节点和兄弟节点【通过具体的节点调用】

  1. parentNode
    1. **属性**,表示当前节点的父节点
  2. previousSibling
    1. **属性**,表示当前节点的前一个兄弟节点
  3. nextSibling
    1. 属性,表示当前节点的后一个兄弟节点

DOM增加

  1. 创建元素节点
    1. createElement()
      1. 可以用于创建一个元素节点对象
      2. 它需要一个标签名作为参数,将会根据该标签名创建元素节点对象,并将创建好的对象作为返回值返回
  2. 创建文本节点
    1. createTextNode()
      1. 可以用来创建一个文本节点对象
      2. 需要一个文本内容作为参数,将会根据该内容创建文本节点,并将新的节点返回
  3. 将新的子节点添加到指定节点
    1. appendChild()
      1. 可以向一个父节点中添加一个新的子节点
      2. 用法:父节点.appendChild(子节点)
  4. 在指定的子节点前面插入新的子节点
    1. insertBefore()
      1. 在指定的子节点前面插入新的子节点
      2. 用法:父节点.insertBefore(新节点,旧节点)
  5. 使用innerHTML也可以完成DOM的增删改的相关操作

DOM替换

  1. replaceChild()
    1. 可以使用指定的子节点替换已有的子节点
    2. 语法:父节点.replaceChild(新节点,旧节点)

DOM删除

  1. removeChild()
    1. 可以删除一个子节点
    2. 语法:父节点.removeChild(子节点)

事件

  • 事件,就是文档或浏览器窗口发生的一些特定的交互瞬间
  • JavaScript与HTML之间的交互是通过事件实现的
  • 对于Web应用来说,有下面这些代表性的事件:点击某个元素、将鼠标移动至某个元素上方、按下键盘上某个键等等

事件对象

  • 当事件的相应函数被触发时,浏览器每次都会将一个事件对象作为实参传递进响应函数中【在事件对象中封装了当前事件相关的一切信息,比如:鼠标的坐标 键盘哪个按键被按下 鼠标滚轮滚动的方向】

事件冒泡【Bubble】

  • 所谓的冒泡指的时事件的向上传导,当后代元素上的事件被触发时,其祖先元素的相同事件也会被触发

  • 在开发中发部分情况冒泡都是有用的,如果不希望发生事件冒泡可以通过事件对象来取消冒泡

    1
    2
    event = event || window.event
    event.cancelBubble=true

事件委派

  • 将事件统一绑定给元素的共同的祖先元素,这样当后代元素上的事件触发时,会一直冒泡到祖先元素 从而通过祖先元素的相应函数来处理事件
  • 事件委派是利用了冒泡,通过委派可以减少事件绑定的次数,提高程序的性能

事件的绑定

  1. 使用对象.事件 = 函数的形式绑定响应函数
    1. 它只能同时为一个元素的一个事件绑定一个响应函数
    2. 不能绑定多个相同的响应函数,如果绑定了多个,则后边会覆盖掉前边的
  2. 使用对象.addEventListener的形式绑定函数
    1. 参数:
      1. 事件的字符串,不要on
      2. 回调函数,当事件被触发时该函数会被调用
      3. 是否在捕获阶段触发事件,需要一个布尔值,一般都传false
    2. 可以绑定多个响应函数
    3. 这个方法不支持IE8及以下的浏览器
      1. 在IE8钟使用attachEvent()来绑定事件
      2. 参数:
        1. 事件的字符串,要on
        2. 回调函数

事件的传播

  • 关于事件的传播网景公司和微软公司有不同的理解

  • 微软公司认为事件应该由内向外传播,也就是当事件触发时,应该先触发当前元素上的事件,然后再向当前元素的祖先元素上传播,也就是事件应该在冒泡阶段执行

  • 网景公司认为事件应该由外向内传播,应该先触发当前元素上最外层的祖先元素的事件,然后再向内传播给后代元素

  • W3C综合了两个公司的方案,将事件传播分成了三个阶段

    1. 捕获阶段
      1. 再捕获阶段时从最外层的祖先元素,目标元素进行事件的捕获,但是默认此时不会触发事件
    2. 目标阶段
      1. 事件捕获到目标元素,捕获结束开始在目标元素上触发事件
    3. 冒泡阶段
      1. 事件从目标元素向他的祖先元素传递,依次触发祖先元素上的事件

    如果希望在捕获阶段就触发事件,可以将addEventListener()的第三个参数设置为true,一般情况下我们不希望如此,所以这个参数一般都是false

BOM

BOM(Browser ObjectModel)即**浏览器对象模型**,它提供了独立于内容而与浏览器窗口进行交互的对象,其核心对象是window

BOM由 一系列相关的对象构成,并且每个对象都提供了很多方法与属性

DOM与BOM的区别

DOM

  • 文档对象模型
  • DOM就是把【文档】当作一个【对象】来使用
  • DOM的顶级对象是document
  • DOM主要学习的是操作页面元素
  • DOM是W3C标准规范

BOM

  • 浏览器对象模型
  • 把【浏览器】当作一个【对象】来看待
  • BOM的顶级对象是window
  • BOM学习的是浏览器窗口交互的一些对象
  • BOM是浏览器厂商在各自浏览器上定义的,兼容性较差

**浏览器对象模型(BOM)API**,用于支持访问和操作浏览器的窗口。总体来说,BOM主要针对浏览器窗口和子窗口(frame),不过人们通常会把任何特定于浏览器的扩展都归在BOM的范畴内。比如,下面就是这样一些扩展:

  • 弹出新浏览器窗口的能力
  • 移动、缩放和关闭浏览器窗口的能力
  • **navigator对象**,提供关于浏览器的详尽信息
  • **location对象**,提供浏览器加载页面的详尽信息
  • **screen对象**,提供关于用户屏幕分辨率的详尽信息
  • **performance对象**,提供浏览器内存占用、导航行为和时间统计的详尽信息
  • 对cookie的支持
  • 其他自定义对象,如**XMLHttpRequest和IE的ActiveXObject**

window对象的常见事件

窗口加载事件

1
2
3
window.onload=function(){}
//或者
window.addEventListener("load",function(){})

**window.onload**是窗口(页面)加载事件,当文档内容完全加载完成会触发该事件(包括图像、脚本文件、CSS文件等),就调用的处理函数

注意:

  1. 有了window.onload就可以把JS代码写道页面元素的上方,因为onload是等页面内容全部加载完毕,再去执行处理函数
  2. window.onload传统注册事件方式只能写一次,如果有多个,会以最后一个window.onload为准
  3. 如果使用addEventListener 则没有限制
1
document.addEventListener('DOMContentLoaded',function(){})

DOMContentLoaded事件触发时,仅当DOM加载完成,不包括样式表,图片,flash等

ie9以上才支持

如果页面的图片很多的花,从用户访问到onload触发可能需要较长的事件,交互效果就不能实现,必然影响用户的体验,此时用DOMContentLoaded事件比较合适

调整窗口大小事件

1
2
window.onresize=function(){}
window.addEventListener("resize",function(){})

**window.onresize**是调整窗口大小加载事件,当触发时就调用的处理函数

注意:

  1. 只要窗口大小发生像素变化,就会触发这个事件
  2. 我们经常利用这个事件来完成响应式布局。window.innerWidth 当前屏幕的宽度

定时器

window对象给我们提供了两个非常好用的方法-定时器

  • setTimeout()
  • setInterval()

1.setTimeout()定时器

1
window.setTimeout(调用函数,[延迟的毫秒数]);

setTimeout方法用于设置一个定时器,该定时器在定时器到期后执行调用函数

注意:

  1. window可以省略
  2. 这个调用函数可以直接写函数、写函数名或者采用字符串’函数名()’三种形式。第三种不推荐
  3. 延迟的毫秒数省略默认时0,如果写,必须是**毫秒**

2.setInterval()定时器

1
window.setTimeout(调用函数,[延迟的毫秒数]);

setTimeout方法用于重复调用一个函数,每隔一段时间,就去调用一次回调函数

注意:

  1. window可以省略
  2. 这个调用函数可以直接写函数、写函数名或者采用字符串’函数名()’三种形式。第三种不推荐
  3. 延迟的毫秒数省略默认时0,如果写,必须是**毫秒**,表示==每隔==多少秒就自动调用这个函数

3.清除定时器

1.停止setTimeout()定时器

1
window.clearTimeout(timeoutId)

举例

1
2
3
4
5
6
7
let btn =document.querySelector("button");
let timer=setTimeout(()=>{
console.log("爆炸了");
})
btn.addEventListener('click',()=>{
clearTimeout(timer);
})

注意:

  1. window可以省略
  2. timeoutId指的是定时器的标识符

2.停止setInterval()定时器

1
window.clearIntervak(intervalId)

JS执行队列

image-20211202152611821

回调函数是异步的

location对象

window对象给我们提供了一个**location属性**用于获取或设置窗体的URL,并且可以用于解析URL。因为这个属性返回的是一个对象,所以我们将这个属性也成为location对象

1.URL

**统一资源定位符(URL)**是互联网上标准资源的地址。互联网上的每个文件都有一个唯一的URL,它包含的信息指出文件的位置以及浏览器应该怎样处理它

URL的一般语法格式为:

1
2
protocol://host[:port]/path/[?query]#fragment
http://www.itcast.cn/index.html?name=andy&age=18#link
组成 说明
protocol 通信协议 常用的http,ftp,maito等
host 主机(域名) www.itcast.cn
port 端口号 可选,省略时使用方案的默认端口 如http的默认端口为80
path 路径 有0或多个’/‘符号隔开的字符串,一般用来表示主机上的一个目录或文件地址
query 参数 以键值对的形式,通过&符号分隔开来
fragment 片段# 后面内容,常见于链接 锚点

2.location对象的属性

属性 返回值
href 获取或者设置整个URL
host 返回主机(域名)
port 返回端口号 如果未写返回空字符串
pathname 返回路径
serach 返回参数
hash 返回片段 #后面内容 常见于链接 锚点

3.location对象的方法

location对象方法 返回值
location.assign() 和href一样,可以跳转页面(也称为重定向页面)
location.replace() 替换页面,因为不记录历史,所以不能后退页面
location.reload() 重新加载页面,相当于刷新页面或者f5 如果参数为true 强制刷新 ctrl+f5

navigator对象包含有关浏览器的信息,它有很多属性,我们最常用的是userAgent,该属性可以返回由客户机发送信息到服务器的user-agent头部的值

history对象

window对象给我们提供了一个history对象,与浏览器历史记录进行交互。该对象包含用户(在浏览器窗口中)访问过的URL

history对象方法 作用
back() 可以后退功能
forward() 前进功能
go(参数) 前进后退功能 参数如果是1 前进一个页面 如果是-1 后退1个页面

JS模块化

介绍

模块化与函数式编程相似,前者是将复杂的程序拆分成块(文件);后者是将复杂的程序封装成函数,都是为了达到复用的效果。块的内部数据是私有的,只有导出接口才能与其它模块通信。

最早,我们是这么写代码的:

1
2
3
4
5
6
7
function foo () {
// ...
}

function bar () {
// ...
}

这样很容易导致全局命名冲突。那定义一个对象,不放到全局中不就好了吗,这就是 Namespace 模式。

1
2
3
4
5
6
var MYAPP =  {
foo: function () {},
bar: function () {}
}

MYAPP.foo()

这样全局变量虽然少了,但本质了是个对象,是可以操作修改的,这样很不安全。

函数是 JavaScript 唯一的 LocalScope,函数外部是修改不了函数内部代码的。所以相对来说是安全的。这就是 IIFE 模式。

1
2
3
4
5
6
7
8
9
10
11
12
// 利用闭包
var Module = (function () {
var _private = 'safe now'
var foo = function () {
console.log(_private)
}

// 暴露接口
return {
foo: foo
}
})()

开发中往往会依赖第三方库,这时候就要引入依赖。例如依赖于 jQuery 时,就要将 jQuery 引入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var Module = (function ($) {
// 使用 jQuery
var _$body = $("body")
var foo = function () {
console.log(_$body)
}

return {
foo: foo
}
// 引入 jQuery
})(jQuery)

Module.foo()

这就是模块模式,也是现代模块实现的基石。

模块化可以降低单个文件的复杂度,降低偶合度,使文件更好维护。当文件分离后可以按需加载,提高了复用性。并且每个模块都是一个独立的作用域,避免了命名冲突。

但是这样就引发了一个问题:文件分离导致需要发送更多的 HTTP 请求,并且使模块与模块之间的依赖变得模糊。

模块化规范就可以通过依赖关系来合并文件,现在有主流的 ==CommonJS、AMD==、CMD、==ES6== 四种规范。

CommonJS模块规范

  • CommonJS 规范中,每个文件都可以当作一个模块。
  • 在服务器端(Node.js),模块的加载是运行时同步加载的;
  • 在浏览器端,没有 require 方法,模块需要使用 Browserify 工具编译打包处理。
  • 使用 require 方法来加载模块,使用 exports 接口对象来导出模块中的成员。

加载(require)

加载模块后,会执行模块中的代码,并得到模块中使用 exports 导出的接口对象。

1
2
3
4
5
6
//require(xxx)
// 加载第三方模块(xxx为模块名)
var unique = require('uniq')

// 加载自定义模块(xxx为模块文件路径)
var myModule = require('./src')

第三方模块会自动去 node_modules 目录下查找。

导出(exports)

只有将模块中的成员导出,才能被其它模块访问。【module.exports=xxx和exports.xxx=value】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1.导出单个成员
module.exports = 'hello';
// 后者会覆盖前者
module.exports = function add(x,y) {
return x+y;
}

// 2.导出多个成员
exports.b = 'hello';
exports.c = function(){
console.log('bbb')
};

// 还可以通过另一种方式导出多个成员
module.exports = {
foo: 'hello',
add:function(){
return x+y;
}
};

编译(browserify)

Browserify (opens new window)可以让你使用类似于 node 的 require() 的方式来组织浏览器端的 Javascript 代码,通过预编译让前端 Javascript 可以直接使用 Node NPM 安装的一些库。

安装:

1
2
3
4
5
// 全局安装
npm install -g browserify

// browserify 还规定要局部安装
npm install browserify --save-dev

使用:

1
browserify  main.js -o bundle.js

最后项目只需要引入打包好的文件就可以了。

CommonJS-Node

  1. 下载node.js

  2. npm init

  3. 如果需要,可以下载第三方模块

    1
    npm install uniq --save
  4. 模块化编程

    • moudle1.js

      1
      2
      3
      4
      5
      6
      7
      //module.exports=xxx
      module.exports={
      msg:"module1",
      foo(){
      console.log("module1")
      }
      }
    • moudle2.js

      1
      2
      3
      4
      //exports.xxx=value
      exports.foo2=function(){
      console.log("m2")
      }
    • module3.js

      1
      2
      3
      4
      //暴露函数
      module.exports=function(){
      console.log("module3s")
      }
    • app.js

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      let module1=require("./modules/module1")
      let module2=require("./modules/module2")
      const module3 = require("./modules/module3")

      module1.foo()
      module2.foo2()
      module3()
      //使用命令node app.js
      //输出module1
      //m2
      //module3s

CommonJS-Browserify

  1. 创建项目结构

  2. 下载borserify

    • 全局:npm install browserify -g
    • 局部:npm install browserify –save-dev
  3. 定义模块代码

    • module.js

  4. 打包处理js:

    1
    browserify js/src/app.js -o js/dist/bundle.js //output
  5. 页面(index.html)中使用引入

    1
    <script type="text/javascript" src="js/dist/bundle.js"

AMD模块规范

AMD 规范专门用于浏览器端,依赖于 require.js,模块的加载是异步的。

定义(define)

通过 define 函数定义模块,定义模块有两种情况:

一种是定义没有依赖的模块,接受一个回调函数

1
2
3
define(function () {

})

另一种是定义有依赖的模块,第一个参数必须是一个数组,数组中就是依赖的模块

1
2
3
define(['module1', 'module2'], function (m1, m2) {

})

加载(require)

使用 require 函数加载模块,

1
2
3
require(['module1', 'module2'], function (m1, m2) {
// 使用 m1、m2
})

导出(return)

使用 return 就可以导出模块中的成员。

1
2
3
define(function () {
return
})

配置(config)

AMD 的实现依赖于 require.js (opens new window)库,通过配置来引用模块。【下载require.js,并引入require.js

创建目录

1
2
3
4
5
6
7
8
9
10
11
12
├─js
│ ├─app
│ ├─sub.js
│ ├─libs
| |-require.js //require.js
| |-angular.js //第三方库
| ├─jquery.js

│ │ ├─canvas.js
│ └─-app.js //主文件
└─index.html

在 html 中引入 require.js。

1
<script data-main="js/app.js" src="js/libs/require.js"></script>

data-main 指定的是主模块的路径,主模块中对依赖进行了配置和使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// app.js
requirejs.config({
// 配置默认路径,最终会与 paths 中的路径拼接
baseUrl: 'js/libs', //基本的路径,出发点
paths: {
app: '../app',
canvas: '../canvas',
jquery: './jquery'
angular: './angular'
},
shim:{
//不是 AMD 模块规范的第三方模块
angular:{
exports:'angular'
}
}
});

// 数组中的项就是上面路径的引用
requirejs(['jquery', 'canvas', 'app/sub'],
function ($, canvas, sub) {
// ...
});

注意:文件不要加扩展名,require.js 会自动拼接 ‘.js’。

需要留意的是,有些第三方库支持 AMD 语法,例如 jQuery:

1
2
3
4
5
if ( typeof define === "function" && define.amd ) {
define( "jquery", [], function() {
return jQuery;
} );
}

表示在 AMD 规范中,将模块名定义为了 jquery,所以使用的时候也必须为 jquery,不能是 jQuery

CMD模块规范

专门用于浏览器端,依赖于 Sea.js。模块的加载是异步的,只有在模块使用时才会加载执行。用法类似于 AMD 和 CommonJS 的结合。

定义(define)

和 AMD 一样使用 require 定义一个模块,但是只接收一个回调函数作为参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1.定义没有依赖的模块
define(function (require, exports, module) {

})

// 2.定义有依赖的模块
define(function (require, exports, module) {
// 引入依赖模块(同步)
var module2 = require('./module2')

// 引入依赖模块(异步)
require.async('./module3', function (m3) {
// 使用 module3
})
})

加载(require)

1
2
3
define(function (require) {
var m = require('./module')
})

导出(exports)

导出的方式与 CommonJS 相似。

1
2
3
4
define(function (require, exports, module) {
export.xxx = value
module.exports = value
})

配置(config)

CMD 依赖于 Sea.js (opens new window),所以要先引入该文件。

1
<script src="./sea.js"></script>

编写模块的配置(config (opens new window))和入口:

1
2
3
4
5
6
7
8
9
10
// seajs 的简单配置
seajs.config({
base: "../sea-modules/",
alias: {
"jquery": "jquery/jquery/1.10.1/jquery.js"
}
});

// 加载入口模块
seajs.use("../js/main");

ES6模块规范

有些浏览器还没有完全适配 ES6 的语法,所以需要 使用 Babel 进行编译打包处理,转换成浏览器能识别的 ES5。转换后的模块引入模块使用的是 require 函数,所以还需要通过 Browserify 进行转换。

前期使用

  1. 定义package.json

  2. 安装babel-clibabel-preset-es2015browserify

    1
    2
    3
    npm install babel-cli browserify -g
    npm install babel-present-es2015 --save-dev
    preset 预设(将es6转换成es5的所有插件打包)
  3. 定义.babelrc文件

    1
    2
    3
    4
    5
    ```
    {
    "presets":["es2015"]
    }
    ​```
  4. 编码【分别暴露与统一暴露一定要用对象解构赋值的方式】

    • 分别暴露

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      //a.js
      export function foo(){
      console.log("foo")
      }
      export function bar(){
      console.log("bar")
      }

      //解构赋值
      import { aa1 , aa2 } from 'a.js'
    • 统一暴露

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      //b.js
      function foo(){
      console.log("foo")
      }
      function bar(){
      console.log("bar")
      }
      export {foo,bar}

      //对象解构引入
      import {foo,bar} from "./b.js"
    • 默认暴露【可以暴露任意数据类型】

      1
      2
      3
      4
      5
      6
      7
      8
      //c.js

      export default function cc(){
      console.log('默认导出');
      }
      //使用定义变量
      import c from 'c.js'
      c.cc()

      PS:export default一个文件中只能出现一次

  5. 编译

    • 使用Babel将ES6编译为ES5代码(但包含CommonJS语法)

      1
      babel js/src -d js/lib
    • 使用Browserify编译js

      1
      browserify js/lib/app.js -o js/lib/bundle.js
  6. 页面引入测试

    1
    <script type="text/javascript" src="js/lib/bundle.js"></script>
  7. 引入第三方模块(jQuery)

    1. 下载jQuery模块

      1
      npm install jquery@1 --save
    2. 在app.js中引入并使用

      1
      import $ form 'jquery'

加载(import)

1
2
3
4
5
// 加载自定义模块
import {foo, arr} from "./src/module"

// 加载第三方模块
import $ from "jquery"

导出(export)

1
2
3
4
5
export function foo () {
console.log("foo")
}

export let arr = [1, 2, 3, 4]

或者还可以将接口统一导出

1
2
3
4
5
6
7
8
9
10
function foo () {
console.log("foo")
}

let arr = [1, 2, 3, 4]

export {
foo,
arr
}

通过以上方法的导出的成员,需要使用对象的解构赋值来接收。

通过 default 导出的数据可以使用普通的变量接收,这种方式只能导出一次。

1
2
3
export default () => {
console.log("默认导出")
}

转换(Babel)

使用 Babel (opens new window)将 ES2015+ 语法的 JavaScript 代码编译为能在当前浏览器上工作的代码。

安装所需的包,从版本 7 开始 Babel 模块都是以 @babel 作为冠名。@babel/core 是 Babel 的核心功能;@babel/cli 是一个命令行工具;@babel/preset-env 用于指导如何将 ES6 转换为 ES5。

1
npm install --save-dev @babel/core @babel/cli @babel/preset-env

在项目的根目录下创建一个命名为 babel.config.json 的配置文件(需要 v7.8.0 或更高版本),并将以下内容复制到此文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"presets": [
[
"@babel/env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
},
"useBuiltIns": "usage",
"corejs": "3.6.5"
}
]
]
}

更多参数配置参见此处

src 目录下的所有代码编译到 lib 目录:

1
./node_modules/.bin/babel src --out-dir lib

你可以利用 npm@5.2.0 所自带的 npm 包运行器将 ./node_modules/.bin/babel 命令缩短为 npx babel

最后还要使用 Browserify 将 require 转换成浏览器可识别的代码。

1
browserfiy lib/app.js -o budle.js

ECMAScript

ES5

严格模式

  1. 理解:
    • 除了正常运行模式(混杂模式),ES5添加了第二种运行模式:“严格模式”(strict mode)。
    • 顾名思义,这种模式使得JavaScript在更严格的语法条件下进行
  2. 目的/作用
    • 消除JavaScript语法的一些不合理,不严谨指出,减少一些怪异行为
    • 消除代码运行的一些不安全之处,为代码的安全保驾护航
    • 为未来新版本的JavaScript做好铺垫
  3. 使用
    • 在全局或函数的第一句语句定义为:’use strict’;
    • 如果浏览器不支持,只解析为一条简单的语句,没有任何副作用;

JSON对象扩展

  1. JSON.stringfy(obj/arr)

    • js对象(数组)转换为json对象(数组)
  2. JSON.parse(json)

    • json对象(数组)转换为js对象(数组)

Object对象扩展

  1. Object.create(prototype,[descriptors])
    • 作用:以指定对象为原型创建新的对象
    • 为新的对象指定新的属性,并对队形进行描述
      • value:指定值
      • writable:表示当前属性值是否是可修改的,默认为false
      • configurable:表示当前属性是否可以被删除,默认为false
      • enumerable:标识当前属性是否能用for in 枚举 默认为false
  2. Object.defineProperties(object,descriptor)
    • 作用:为指定对象定义扩展多个属性
    • get:用来获取当前属性值得回调函数
    • set:修改当前属性值得触发回调函数,并且实参即为修改后的值
    • 存取器属性:setter,getter 一个用来存值,一个用来取值

Array数组扩展

  1. Array.prototype.indexof(value):得到值在数组中的第一个下标
  2. Array.prototype.lastIndexOf(value):得到值在数组中的最后一个下标
  3. Array.prototype.forEach(function(){item,index}):遍历数组
  4. Array.prototype.map(function(item,index){}):遍历数组返回一个新的数据,返回加工之后的值
  5. Array.prototype.fliter(function(item,index){}):遍历过滤出一个新的子数组,返回条件为true的值

Function函数扩展

  1. Function.prototype.bind(obj):将函数内的this绑定为obj,并将函数返回
  2. 面试题:bind(),call()和apply()的区别?
    • 都能指定函数的this
    • call()/apply()是立即调用函数
    • bind()是将函数返回
  3. bind一般用于回调函数修改this指向

ES6

解构赋值

  1. 理解:

    • 从对象或数组中提取数据,并赋值给变量(多个)
  2. 用途

    • 给多个形参赋值
  3. 使用

    • 可以赋默认值

      1
      2
      3
      4
      5
      6
      let [a,b="lisi"]=["张三"]
      //a:张三 b:lisi
      let [a,b="lisi"]=["张三"undefined]
      //a:张三 b:lisi
      let [a,b="lisi"]=["张三"null]
      //a:张三 b:null
    • 声明变量时,需要使用对象解构,需要在解构外面加上圆括号,不然会报错

      1
      2
      let foo;
      ({foo}={foo:"1234"});

对象的解构赋值

1
let {n,a}={n:'tom',a:12}

数组的解构赋值

1
2
let [a,b]=[1,'dididi'];
let [a,c,c]="abc"

Promise对象

  1. 理解:

    • Promise对象:代表了未来某个将要发生的事件(通常是一个异步操作)
    • 有了Promise对象,可以将异步操作以同步的流程表达出来,避免了层层嵌套的回调函数(俗称“回调地狱”)
    • ES6的Promise是一个构造函数,用来生成promise实例
  2. 使用promise基本步骤(2步):

    • 创建promise对象

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      let promise=new Promise((resove,reject)=>{
      //初始化promise状态为pending
      //执行异步操作(例如发送ajax请求,开启定时器)
      if(异步操作成功){
      resolve(value);//修改promise的状态为fullfilled
      }else{
      reject(errMsg);//修改promise的状态为eejected
      }
      })

    • 调用promise的then()

      1
      2
      3
      4
      5
      6
      promise
      .then(()=>{//成功的回调

      },(){//失败的回调

      })
  3. promise对象的3个状态

    • pending:初始化状态
    • fullfilled:成功状态
    • rejected:失败状态

TypeScript

  • 以JavaScript为基础构建的语言
  • JavaScript的超集
  • 可以在任意支持JavaScript的平台中执行
  • TypeScript扩展了JavaScript,并添加了类型
  • TS不能被JS解析器直接执行

TypeScript环境搭建

  1. 下载安装Node.js
  2. 使用npm 全局安装TypeScript
    • 进入命令行
    • 输入:npm i -g typescript
  3. 创建一个ts文件
  4. 使用tsc对ts文件进行编译
    • 进入命令行
    • 进入ts文件所在目录
    • 执行命令:tsc xxx.js

类型声明

  • 类型声明

    • 类型声明是TS非常重要的一个特点

    • 通过类型声明可以指定TS中变量(参数、形参)的类型

    • 指定类型后,党委变量赋值时,TS编译器会自动检查值是否符合类型声明,符合就赋值,否则报错

    • 简而言之,类型声明给变量设置了类型,使得类型只能存储某种类型的值

    • 语法:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      let 变量:类型;
      let 变量:类型=值;
      function fn(参数:类型,参数:类型):类型{
      ...
      }

      let a:number;
      a=5;
      a="string"
      //不能将类型“string”分配给类型“number”。
      //声明一个变量并直接赋值
      let b=true;
      b=3
      //Type '3' is not assignable to type 'boolean'.
  • 自动类型判断

    • TS拥有自动的类型判断机制
    • 当对变量的声明和赋值是同时进行的,TS编译器会自动判断变量的类型
    • 所有如果你的变量的声明和赋值是同时进行的,可以省略掉类型说明
  • 类型:

    类型 例子 描述
    number 1,-33,2.5 任意数字
    string “hi”,’hi’,hi 任意字符串
    boolean true,false 布尔值true或false
    字面量 其本身 限制变量的值就是改字面量的值
    any * 任意类型
    unknown * 类型安全的any
    void 空值(undefined) 没有值(或undefined)
    never 没有值 不能是任何值
    object {name:’孙悟空’} 任意的JS对象
    array [1,2,3] 任意JS数组
    tuple [4,5] 元素,TS新增类型,固定长度数组
    enum enum{A,B} 枚举,TS中新增类型
    1. number

      1
      2
      let a: number;
      a = 10;
    2. string

      1
      2
      3
      let b: string;

      b = 'hello';
    3. boolean

      1
      2
      3
      let c= false;

      c = true;
    4. 字面量

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      let a1: 10;

      a1 = 10;//后面使用时不可修改,类似常量


      //可以使用 | 来连接多个类型(联合类型)
      let b1: "male" | "female";
      b1= "male";
      b1= "female";

      let c1 : boolean | string;
      c1 = true;
      c1 = 'hello';
    5. any

      1. any 表示的是任意类型,一个变量设置类型为any后相当于对该变量关闭了TS的类型检测
      2. 使用TS时,不建议使用any类型(尽量避免)
      1
      2
      3
      4
      5
      6
      7
      // let d:any;(显示的any)

      //声明变量如果不指定类型,则TS解析器会自动判断变量的类型为any(隐式的any)
      let d;
      d = 10;
      d = 'hello';
      d = 'true';
    6. unknown

      1
      2
      3
      4
      5
      //unknown表示未知类型的值
      let e: unknown;
      e = 10;
      e = true;
      e = "hello";

      any类型的变量可以赋值给任意变量;但unknown 类型的变量不能直接赋值给其他变量

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      let s:string;

      //d的类型是any,它可以赋值给任意变量
      let d;
      s = d;

      let e: unknown;
      e = 'hello';
      s = e;//会报错,e的类型是unknown,不能直接赋值给其他变量

      if (typeof e === "string") {
      s = e;
      }

      类型断言—可以用来告诉解析器变量的实际类型【可针对于unknow类型】

      1
      2
      3
      4
      5
      6
      7
      8
      9
      /*
      语法:
      1.变量 as 类型
      */
      s = e as string;
      /*
      2.<类型>变量
      */
      s = <string>e;
    7. void【一般用于设置函数返回值】

      void 用来表示空值,以函数为例,就表示没有返回值(或返回undefined)的函数

      1
      2
      3
      function fn2(): void{

      }
    8. never【一般用于设置函数返回值】

      never 表示永远不会返回结果;没有值(比较少用,一般是用来抛出错误)

      1
      2
      3
      function fn3(): never{
      throw new Error("报错了!");
      }
    9. object

      1. { } 用来指定对象中可以包含哪些属性

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        /*  
        语法:{属性名:属性值,属性名:属性值}
        在属性名后面加上?,表示属性是可选的
        */
        let b: {name: string, age?:number};

        b = {}; //没有的话就会报错
        b = {name: "孙悟空", age: 18};

        let c1: {name: string, a?:number, b?:number};
        c1 = {name:"猪八戒", a:1, b:2,c:3} //会报错,多余了c

      2. [propName: string]: any 表示可以多余任意类型的属性(propName可以修改为任意字段名)

        1
        2
        let c: {name: string, [propName: string]: any}
        c = {name:"猪八戒", age: 18, gender: '男'}
    10. 设置函数结构的类型声明

      1
      2
      3
      4
      5
      6
      7
      8
      9
      /* 
      语法:
      (形参:类型,形参:类型...)=> 返回值
      */
      let d1: (a: number ,b: number)=>number;

      d1 = function (n1: number, n2: number): number {
      return n1 + n2
      }
    11. array

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      /* 
      数组的类型声明:
      1.类型[]
      2.Array<类型>
      */

      //string[] 表示字符串数组
      let e1:string[];
      e1 = ['a','b','c'];

      //number[] 表示数值数组
      let f: number[];

      let g: Array<number>;
      g = [1, 2, 3];
    12. tuple(ts新增类型)

      tuple(元组):就是固定长度的数组

      1
      2
      3
      4
      5
      6
      /* 
      语法:[类型, 类型, 类型]
      */

      let h: [string, number];
      h = ['hello', 123];
    13. enum(ts新增类型)

      枚举可以把所有可能的值都列举出来

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      enum Gender{ //定义枚举类型可以把所有可能的值都列举出来
      Male = 0,
      Female = 1,
      }

      let i: {name: string, gender: Gender};
      i = {
      name: '孙悟空',
      gender: Gender.Male
      }

      console.log(i.gender === Gender.Male)

      & 表示同时满足

      1
      2
      let j: {name: string} & {age: number};
      j = {name: '孙悟空', age:18}

      类型的别名

      1
      2
      3
      4
      5
      6
      type myType = 1 | 2 | 3 | 4 | 5;
      let k: myType;
      let l: myType;
      let m: myType;

      k = 2;

编译选项

1.自动编译的两种方法

  1. tsc ts文件 -w 能够对单个ts文件进行监视,若有修改则会自动重新编译。
  2. 新建一个tsconfig.json文件,然后在命令行执行tsc -w 可以对所有ts文件进行监视,若有修改则会自动重新编译。

2.tsconfig.json文件

tsconfig.json 是ts编译器的配置文件,ts编译器可以根据它的信息来对代码进行编译

  1. include

    1. 用来表示需要被编译的ts文件目录
    2. 路径: /**表示任意目录, /*表示任意文件
    1
    2
    3
    "include": [
    "./src/**/*"
    ]
  2. exclude

    1. 用来表示不需要被编译的文件目录
    2. 默认值:[“node_modules”, “bower_components”, “jspm_packages”]
    1
    2
    3
    "exclude": [
    "./src/hello/**/*"
    ]
  3. extends

    • 定义被继承的文件
    1
    2
    //表示当前配置文件中会自动包含config目录下base.json中的所有配置信息
    "extends": "./configs/base"
  4. files

    • 指定被编译文件的列表,只有需要编译的文件少时才会用到
    1
    2
    3
    4
    5
    "files": [
    "core.ts",
    "sys.ts",
    "types.ts"
    ]
  5. **compilerOptions**(重要,编译器的选项)

    compilerOptions有很多的子选项

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    "compilerOptions": {
    //target 用来指定ts被编译为ES的版本
    //'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'esnext'.
    "target": "es2015",

    //module 指定要使用的模块化的规范
    //'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015', 'es2020', 'esnext'
    "module": "es2015",

    //lib 用来指定项目所用的库
    // "lib": [],//一般情况下不需要设置(浏览器运行的就不用管,nodejs运行的再根据实际使用去指定)
    // 可选值:'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'esnext', 'dom',
    // 'dom.iterable', 'webworker', 'webworker.importscripts', 'webworker.iterable', 'scripthost', 'es2015.core', 'es2015.collection',
    // 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include',
    // 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'es2018.asyncgenerator', 'es2018.asynciterable', 'es2018.intl',
    // 'es2018.promise', 'es2018.regexp', 'es2019.array', 'es2019.object', 'es2019.string', 'es2019.symbol', 'es2020.bigint', 'es2020.promise',
    // 'es2020.sharedmemory', 'es2020.string', 'es2020.symbol.wellknown', 'es2020.intl', 'esnext.array', 'esnext.symbol', 'esnext.asynciterable', 'esnext.intl',


    //outDir 用来指定编译后文件所在的目录
    "outDir": "./dist",

    //outFile 将代码合并为一个文件
    // 设置outFile后,所有的全局作用域中的代码会合并到同一个文件中
    // "outFile": "./dist/app.js",

    // 是否对js文件进行编译,默认是false
    "allowJs": true,

    // 是否检查js代码是否符合语法规范,默认值是false
    "checkJs": true,

    // 是否移除注释
    "removeComments": true,

    // 不生成编译后的文件
    "noEmit": false,

    // 当有错误时不生成编译文件
    "noEmitOnError": true,

    // 所有严格检查的总开关,包括下面四个(如果相同的话可以直接用这个,下面四个省略)
    "strict": true,

    // 用来设置编译后的文件是否使用严格模式,默认是false
    "alwaysStrict": true,

    // 不允许隐式的any类型
    "noImplicitAny": true,

    // 不允许不明确类型的this
    "noImplicitThis": true,

    // 严格检查空值
    "strictNullChecks": true

    }