目录(该目录点击后跳转内容可能不匹配,可使用Ctrl+F进行检索)
3. 函数的隐形参数arguments(只在function函数内)
Document对象及其方法/属性(document表示整个html页面)
4. createElement方法与appendChild方法
一、JavaScript介绍
JavaScript诞生主要是为了完成页面的数据验证,因此它运行在客户端,需要运行浏览器来解析执行JavaScript代码
- 首先,JavaScript是一门单独的语言,与java无关
- 与java是一门强类型的语言不同的是,js(即JavaScript)是一门弱类型的语言
这里所提到的弱类型指的是类型可变,JavaScript不像java是强类型
强类型就是定义变量时,类型已确定且不可变
JavaScript特点:
-
交互性(它可以实现信息的动态交互)
-
安全性(它不允许直接访问本地磁盘)
-
跨平台性(只要是可以解释js的浏览器都可以执行,与平台无关)
二、JavaScript与html代码的结合方式
2.1 第一种方式
只需要在head标签或者是body标签内使用script标签书写JavaScript代码
效果如下:
2.2第二种方式
使用script标签引入单独的JavaScript标签
效果如下
值得注意的是:
script标签可以用来书写JavaScript代码,也可以用来引入JavaScript文件,但是注意这二者只能二选一,不能同时使用两个功能
补充 三种常见输出语句:
上图是JavaScript的三种常用的输出语句
三、变量
变量是存放某些值的内存的命名
JavaScript的数据类型:
- 数值类型(number包含整数和浮点数):基本与java一样有int,short,long,float,double等
- 字符串类型(string)
- 对象类型(object)
- 布尔类型(boolean)
- 函数类型(function):这里与java不同,JavaScript的变量可以指向一个函数
JavaScript的特殊值:
- undefined 未定义,所以JavaScript的变量在未赋值时,默认都是undefined
- null 空值
- NaN 全称是: Not a number 即非数字、非数值
补充 Undefined 与 Null 与 NaN:
- Null(空值)类型的值只有一个,就是null,它专门用来表示一个为空的对象,使用 typeof 检查一个null值时,会返回object
- Undefined(未定义)类型的值只有一个,就是undefined,当声明一个变量,但是并不给变量赋值时,它的值就是undefined,使用 typeof 检查一个undefined值时,会返回undefined
- NaN(非number)是 Not a Number 的简写,表示“非数值”,使用 typeof 检查一个NaN值时,会返回number
JavaScript的定义变量格式
var 变量名;
var 变量名 = 值;
复制代码
这里的typeof函数作用是得到参数的数据类型
补充 转义字符![](https://img-blog.csdnimg.cn/11c0628b551c421592636c9800b3e9be.png)
js中也与C语言或java语言一样有转义字符,如上图常见的转义字符
补充 进制表示
与其他语言类似:
js中的十六进制数字 需要在数字前加上“0x”
八进制数字需要在数字前加上“0”
二进制数字需要在数字前加上“0b”(不常用)
补充 类型转换
1. 其他数据类型转成String
- 方式一
调用被转换数据类型的toString()方法
该方法不会影响到原变量,它会将转换的结果返回
但是注意:null和undefined这两个值没有toString()方法,如果调用toString则会报错
- 方式二
调用String()函数,并且将被转换的数据作为参数传递给函数
使用String()函数做类型转换时:
对于Number和Boolean实际上就是调用的toString()方法
但是对于null和undefined,就不会调用toString()方法,它会将null和undefined直接转换为“null”和“undefined”
方式二与方式一的区别即在于能否转换null与undefined
2.其他数据类型转成Number
- 方式一
使用 Number()函数进行转换
---字符串--->>>----数字
1)如果是纯数字的字符串,则直接将其转换为数字
2)如果字符串中带有非数字的内容,则转换为 NaN
3)如果字符串是一个空串/一个全是空格的字符串 ,则会返回 0
布尔值--->数字
1)true转成1
2)false转成0
null转成0
undefined转成NaN
- 方式二
使用 parseInt()把一个字符串转换为一个整数
使用parseFloat()把一个字符串转换为一个浮点数
这种方式专门用于对付字符串,它们会将 到第一个非数字字符前的字符提取出来变成数字
如果对非字符串类型使用上述两种方法
它会将其先转换为String再进行操作
3.将其他数据类型转换为Boolean
使用Boolean()函数进行转换
1)数字-->布尔值 除了0和NaN,其余都是 true
2)字符串-->布尔值 除了空串,其余都是true
3)null与undefined都会转换为false
4)对象object也会转换为true
补充 算术运算符
- 例如我们前面所使用的typeof就是运算符,它用来获得一个值的类型,并且将该值的类型以字符串的形式返回
- 当对非Number类型的值进行运算时,会将这些值转换为Number再进行运算,但是特殊的是:任何值与NaN做运算都得NaN
- 如果对存在字符串类型的元素进行运算时,会将两个字符串拼接为一个字符串并返回,这点与java一样
补充 一元运算符
+ 正号 正号不会对数字产生任何影响
- 负号 它可以对数字进行符号的取反
对于非Number类型的值使用该运算符,它会先将其转换为Number然后再运算
借助这一点我们可以对一个其他的数据类型使用+来将其转换为Number类型
原理与Number()一样
补充 使用Unicode编码
四、关系(比较)运算符
JavaScript的关系(比较)运算符与java基本一致,都有> >= < <= !=
需要注意的点是,关系运算符的比较中如果出现非数字的元素,则它会先将其转换成Number再进行比较,特殊的是 任何值与 NaN作比较都是返回false
其次,如果符号两侧的值都是字符串时,不会再将其转换成数字进行比较,而是直接比较字符串中字符的Unicode编码
因此在比较字符串型的数字时一定要先进行转型
值得注意的是下面这两个运算符
- 等于 == 等于是简单地做字面值的比较
- 全等于 === 全等于除了做字面值的比较外,还做数据类型的比较
同样的,当使用== 或 != 来比较两个值时,如果值的类型不同,则会自动进行类型转换,将其转为相同的类型,然后再比较,一般是转成Number
但是===不会进行类型转换
否定的全等是 !==
五、逻辑运算符
这里如果我们对非布尔值进行 取反运算 ,它会先将其转换为布尔类型,然后再取反,因此我们可以利用该特点来将一个其他数据类型转换为布尔类型,只需对其使用两次取反!!
原理与Boolean()一样
下面介绍的是非布尔类型的与或运算,这里是与之前所学的java不同的,要注意
补充 条件运算符(三元运算符)
与其他语言基本一致,这里不再赘述
补充 代码块![](https://img-blog.csdnimg.cn/67fe37b658c04588a6f9310f85147cd3.png)
补充 基本数据类型与引用数据类型
- 当我们比较两个基本数据类型时,就是比较值
- 而比较两个引用数据类型时,它是比较的对象的内存地址
- 如果两个对象是一模一样的,但是地址不同,它也会返回false
六、数组(Array)
1.构造函数创建数组
- 数组也是对象
- 它和普通对象功能类似也是用来存储一些值的
- 不同的是普通对象是使用字符串作为属性名的,而数组是使用数字作为索引操作元素
- 数组的存储性能比普通对象要好,在开发中我们经常使用数组来存储一些数据
也可以在创建数组时就初始化数组
要注意的是,当传入的参数只有一个的意思与上面不同时
与对象属性的读取一样,如果读取不存在的索引,不会报错而是返回undefined,因为读取的操作与创建的操作一样,读取相当于创建但是没有赋值,所以返回undefined
对于连续的数组,我们可以使用length属性来获取数组长度
对于非连续的数组,使用length属性会获取到 最后一个元素的索引+1,因此尽量不要创建非连续数组
此外,length属性还可以被 设置/修改
2.数组字面量
这里只对JavaScript与java不同的地方进行强调
任意数据类型也包括 对象、函数
- JavaScript的数组,只要我们通过数组下标进行赋值,那么最大的下标值,就会自动地给数组做扩容操作
- 数组的遍历初始化时要使用var定义
补充 forEach()遍历
通常来说,我们遍历数组会采用for循环遍历,除此之外,JS还为我们提供了一个forEach()用来遍历数组(需要注意兼容性问题)
- forEach()方法需要一个函数作为参数(我们通常使用匿名函数作为参数)
- 传入的参数/函数 会被浏览器自主调用,像这种由我们创建的,但是不由我们调用的函数我们称为 回调函数
- 数组中有几个元素,函数就会执行几次,每次执行时,浏览器会将遍历到的元素 以实参的形式传递进来,我们可以定义形参来读取这些内容
浏览器会在回调函数中传递3个参数:
- 第一个参数,就是当前正在遍历的元素
- 第二个参数,就是当前正在遍历的元素索引
- 第三个参数就是当前正在遍历的数组
3.Array对象常用方法
1)push
2) pop
3) unshift
4) shift
5) 切片slice
传递负数时,-1代表倒数第一个,-2代表倒数第二个,以此类推
6)splice
- 该方法可以用于删除、替换、插入元素
- 使用splice()会影响到原数组,会将指定元素删除、替换、插入,如果是删除会将被删除的元素返回
参数:
- 第一个参数,表示开始位置的索引
- 第二个参数,表示删除的数量
- 第三个及以后,可以传递一些新的元素,这些元素将会自动插入到开始索引前面
7)concat
8) join
9) reverse
10) sort
七、函数
补充 使用构成函数创建一个函数对象
1.使用function关键字定义函数
-
函数定义时如果有参数,我们无需写类型,给出有多少个参数即可
-
函数若需要返回,直接使用return即可
-
JavaScript中不允许重载,如果写了两个一样的函数那么第二个函数将覆盖第一个函数的定义!!!
-
直接进行演示:
2.将函数赋给var
该种方式定义一个函数实际上就是 声明了一个“匿名函数”同时将其赋给了一个变量
3. 函数的隐形参数arguments(只在function函数内)
在调用函数时,浏览器每次都会传递进两个隐含的参数:
- 函数的上下文对象this
- 封装实参的arguments
隐形参数即 在function函数中不需要定义,但却可以直接用来获取所有参数的变量
- 隐形参数与java基础的可变长参数一样(public void fun(Object...args);),它实际上是一个类数组对象,因此对其操作类似操作数组
- arguments有一个属性叫做 callee,这个属性对应一个函数对象,就是当前正在执行的函数对象
下面进行演示(需求是编写一个函数可以计算传入参数之和)
这里没有说明传入有几个参数,所以使用隐形参数
补充 立即执行函数(匿名函数的使用)
有时候我们会希望有一个函数在使用上只会被使用一次,使用过之后便不会再被使用,并且定义完后立即调用
这个时候我们就可以使用立即执行函数,实际上,我们如果直接将匿名函数写出来的话,在语法上是无法通过的,我们需要给匿名函数加上一个括号括起来,这样浏览器才会认为它是一个匿名函数,这样我们便得到了匿名函数的对象,此时要想调用这个对象只需在这个对象的后面加上一个括号即可调用
补充 函数使用注意事项
- 在调用函数时解析器/浏览器不会检查实参的类型与个数,因此我们通常需要在函数里检查参数是否合法
在开发中我们经常会使用到将一个匿名函数作为一个实参传给一个函数如下
此时我们需要注意以下两种情况
补充 函数对象的方法call()与apply()
- call()和apply()这两个方法都是函数对象的方法,都需要通过函数对象来调用
- 当对函数调用这两个函数时,都会调用函数执行
- 在调用call()和apply()可以将一个对象指定为第一个参数,并且这个对象会成为函数执行时的this
- call()方法可以将实参在对象后依次传递(如果在指定完对象后,还需要传参)
- apply()方法需要将实参封装到一个数组中进行传递(如果在指定完对象后,还需要传参)
八、自定义对象
对象属于一种引用数据类型,在对象中可以保存多个不同数据类型的属性
补充 对象的分类
1.object形式的自定义对象
值得注意的是,我们如果读取对象中没有的属性,不会报错而是会返回undefined
2.花括号形式的自定义对象(对象字面量)
对象的删除:
注意冒号和逗号的使用
补充 对象的属性名与属性值
- 对象的属性名不强制要求遵守标识符的规范,什么乱七八糟的名字都可以用,但是我们还是尽量遵守规范
这是访问属性的另外一种方式
补充 in运算符
值得注意的是,使用in检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true
但是这样的话我们使用in运算符就不能确定含有的属性是不是在对象自身里面了
因此我们引出另外一个函数 hasOwnProperty("属性名") 该方法只有在对象自身含有指定的属性名时才会返回true
补充 利用for in枚举对象中的属性
补充 声明提前
1.变量的声明提前
如图上面这段代码中,我们在输出a变量之前并没有声明a变量,此时理应代码报错,但是并没有,就是因为变量的声明提前机制简单来说就是原来的代码实际上相当于上图这段代码
2.函数的声明提前
函数的声明提前指的是函数的调用可以写在函数的定义的前面
与变量的声明提前同理 但是这里主要注意的是只有使用“函数声明形式”创建的函数才会被声明提前
而使用函数表达式创建的对象不会被函数声明提前,而只会被变量声明提前
补充 作用域
1.全局作用域
- 直接编写在script标签中的JS代码,都在全局作用域内
- 全局作用域在页面打开时创建,在页面关闭时销毁
- 在全局作用域中有一个全局对象window,它代表的是一个浏览器的窗口,它由浏览器创建,我们可以直接使用
- 在全局作用域内,创建的变量/函数都会作为window对象的属性/方法保存
- 全局作用域中的变量都是全局变量,在页面的任意部分都可以访问得到
2.函数作用域
-
调用函数时创建函数作用域,函数执行完毕后,函数作用域销毁
-
每调用一次函数都会创建一个新的函数作用域,它们之间是相互独立的
-
在函数作用域中可以访问到全局作用域的变量,反之不行
- 当在函数作用域中操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用;如果没有则向上一级作用域中寻找,直到找到全局作用域;如果全局作用域中依然没有,则会报错ReferenceError
- 在函数作用域中也有声明提前的特性,即使用var关键字声明的变量,会在函数中所有的代码执行之前被声明;函数声明也会在函数中所有代码执行之前执行(简而言之就是函数作用域内同样拥有变量声明提前与函数声明提前的机制)
- 在函数中,不使用var声明的变量都会成为全局变量
- 定义形参就相当于在函数作用域用关键字var声明了该形参
如上面两图的d实际上都是全局变量,效果一样
补充 构造函数
用于创建对象的函数我们称之为构造函数
- 构造函数就是一个普通的函数,创建方式和普通函数没有区别
- 不同的是构造函数命名时 首字母习惯大写
构造函数的执行流程:
- 立即创建一个新的对象
- 将this指向这个新建的对象,因此在构造函数里可以使用this来操作/引用新建的对象
- 逐行执行函数中的代码
- 将新建的对象返回
通常来说,使用同一个构造函数创建的对象,我们称为一类对象,也将一个构造函数称为一个类
我们将通过一个构造函数创建的对象,称为是该类的实例
这里有一点值得注意的是,所有的对象都是Object的后代,所以任何对象和Object左instanceof检查时都会返回true,这点与java相似
构造函数的方法优化
如上图所示,我们创建了一个类里面含有一个方法sayName(),我们不难发现,每当我们创建一个新的实例的时候,它们其实都是不同的对象,也就是说构造函数每执行一次,都会创建新的sayName()方法,执行10000次就会有10000个新的方法,而且,这些方法所实现的内容其实是重复的,这样来说其实是会比较占用我们的内存空间的
因此我们提出优化,我们将sayName()方法从“类”中提取出来,成为一个公共的函数
具体代码实现如下:
但是,我们这么写也存在一定的问题:
- 将函数定义在全局作用域内,污染了全局作用域的命名空间
- 函数定义在全局作用域内也不安全
因此就引出我们下面学习的内容
补充 原型对象
我们所创建的每一个函数(任何函数),解析器都会向函数中添加一个属性prototype,这个属性对应着一个对象,这个对象就是我们所谓的原型对象
- 如果函数作为一个普通函数调用prototype没有任何作用
- 当函数以构造函数的形式调用时,它所创建的实例/对象中都会有一个隐含的属性,指向该构造函数的原型对象,我们可以通过__proto__来访问该属性
- 原型对象就相当于一个 公共区域,所有同一个“类”的实例都可以访问到这个原型对象,我们可以将对象中 共有 的内容,统一设置到原型对象中(这里其实就类似其他语言的“继承”)
- 当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用;如果没有则会去原型对象中寻找,如果找到则直接使用
以后我们创建构造函数时,可以将这些对象共有的属性和方法,统一添加到构造函数的原型对象中,这样不用分别为每个对象添加,也不会影响到全局作用域,就可以使得每个对象都具有这些属性和方法了
下面是利用原型对象解决前面 构造函数方法优化 的问题
原型的原型
- 原型对象也是对象,因此它也有原型
- 当我们使用一个对象的属性或方法时,会先在自身查找
- 自身中如果有,则直接使用
- 如果没有则去原型对象中寻找,
- 如果原型对象中有则使用
- 如果没有则去原型的原型中寻找,以此类推一直找到Object的原型对象,在通过Object的原型对象的原型最终指向的是null
__ proto __对象原型的意义是为对象成员查找机制提供一个方向,或者说是一条线路
优先使用对象身上的成员,就近原则
- Object对象的原型 没有原型,如果Object原型中仍然没有找到则返回undefined
原型链
prototype原型对象里面的__ proto __ 原型指向的是Object.prototype;
Object.prototype原型对象里面的__ proto __ 原型指向为null
- 只要是对象都会有一个原型__ proto __,指向原型对象prototype;
- 原型对象也有一个原型 __ proto __,指向Object原型对象prototype;
- Object的原型对象的原型 __ proto __指向为null
这里原型链的知识参考自 原型、原型对象和原型链_Mae_cpski的博客-CSDN博客
补充 事件对象
- 当事件的响应函数被触发时,浏览器每次都会把一个事件对象作为实参传递进响应函数,在事件对象中封装了当前事件相关的一切信息,比如:鼠标的坐标、键盘哪个按键被按下、鼠标滚轮滚动的方向
要想得到某个事件的事件对象的话,我们只需定义形参将实参接收下即可使用
补充 事件的绑定(addEventListener() )
我们都知道当我们为同一个对象绑定同一个事件的多个响应函数时,如果我们使用对象.οnclick=function(){}会导致,最终绑定成功的只有我们最后定义的那个函数,前面的都被覆盖了
通过addEventListener()这个方法也可以为事件绑定响应函数
参数
- 事件的字符串(注意不要"on")
- 回调函数,当事件触发时该函数会被调用
- 是否在捕获阶段触发事件(布尔值),通常传false
使用addEventListener()可以同时为一个对象的相同事件绑定多个响应函数
这样当事件被触发时,响应函数将会按照函数的绑定顺序执行
补充 包装类
在JS中,JS为我们提供了三个包装类,通过这三个包装类可以将基本数据类型的数据转化为对象
- String()
- 可以将基本数据类型字符串转换为String对象
- Number()
- 可以将基本数据类型的数字转换为Number对象
- Boolean()
- 可以将基本数据类型的布尔值转换为Boolean对象
但是注意:
在实际开发中,我们并不会去使用基本数据类型的对象,如果使用基本数据类型的对象,在做一些比较时可能会带来一些不可预期的结果(对象的比较 比较的是对象的地址)
那么它有什么用呢???
当我们对一些基本数据类型的值去调用属性和方法时,浏览器会自动地 临时使用包装类将其转换成对象,然后再调用对象的属性和方法 ,并且在调用完以后,再将其转回基本数据类型
如图,我们对基本数据类型的s变量调用了toString()方法,并没有报错
如图我们为基本数据类型的变量s添加了一个属性hello,并且再访问hello属性,但是这里输出的却是undefined
这里的添加属性hello 和 访问属性hello 实际上 浏览器临时创建了两个对象,因此在访问属性时和创建对象时的对象其实不是一个对象,因此返回undefined
补充 Date对象
- 在JS中我们经常使用Date对象来表示一个时间
- 如果我们直接使用构造函数创建一个Date对象,则会封装为当前代码执行的时间
- 我们也可以创建一个指定的时间对象,只需将我们要设定的时间作为参数传递给构造函数即可(注意格式)
常用Date对象方法
1)getDate()
获取当前日期对象是几日
2)getDay()
获取当前日期对象是星期几(返回0~6的值,其中0表示周日)
3)getMonth()
获取当前日期对象是几月(返回0~11的值,其中0表示1月)
4)getFullYear()
获取当前日期对象的年份
5)各种get时分秒
6)getTime()
- 获取当前日期对象的时间戳
- 时间戳是指从格林威治标准时间的1970年1月1日,0时0分0秒到当前日期所花费的毫秒数
补充 Math对象
- Math和其他的对象不同,它不是一个构造函数,它是一个“工具类”,我们无需创建对象即可使用,它里面封装了和数学运算相关的属性和方法
属性如下:
常用方法如下:
1)abs()
可以用来计算一个数的绝对值
2)ceil()
可以对一个数进行向上取整
3)floor()
可以对一个数进行向下取整
4)round()
可以对一个数进行 四舍五入取整
5)random()
可以随机生成一个0~1的随机数(不包含0和1)
6)max()
可以获取多个数中的最大值
7)min()
可以获取多个数中的最小值
8)pow(x,y)
可以返回x的y次幂
9)sqrt()
可以对一个数进行开方运算
补充 字符串对象
值得强调的是,字符串在底层是以 字符数组 的形式保存的,即类似[ 'H' , 'E' , 'L' , 'L' , 'O' ]这样,那么既然字符串的底层是 数组 的话,字符串也拥有数组的属性length用于得到 字符串长度,其次,也可以通过 数组名[下标] 来访问字符串中的字符
字符串的常用方法:
1)charAt()
- 可以返回字符串中指定位置的字符
- 根据索引获取指定的字符(效果与使用[ ]一样)
2)charCodeAt()
- 获取指定位置字符的字符编码(Unicode编码)
3)fromCharCode()
- 可以根据字符编码去获取字符,与上面的方法不同的是,该方法是属于构造函数对象String的
4)concat()
- 可以用来连接两个或多个字符串(作用和+号一样)
5)indexOf()
- 该方法可以检索一个字符串是否含有指定内容
- 如果字符串含有该内容,则会返回其第一次出现的索引
- 如果没有找到则返回-1
- 除了传入要查找的内容作为参数外,还可以传入第二个参数代表从哪个字符开始往后找
6)lastIndexOf()
- 该方法的用法与indexOf()一样,不同的是indexOf()是从前往后找,而lastIndexOf()是从后往前找,它也可以指定开始查找的位置
7)slice()
- 该方法可以从字符串中截取指定的内容,并且不会影响原字符串,而是将截取内容返回
参数:
- 第一个,开始的索引(包括开始位置)
- 第二个,结束的索引(不包括结束位置,该参数可以省略,省略后则截取到开始索引后面全部)
- 也可以传递一个负数作为参数,负数的或会从后往前计算
8)substring()
该方法与slice()基本一样,这里只强调它们的不同点:
- substring()不能接收负值作为参数,如果传递了一个负值给substring(),它会自动将其处理为0
- substring()会自动调整参数的位置使参数从小到大
9)split()
该方法与数组中的方法join()作用一样
可以将一个字符串拆分为一个数组
参数:
需要一个字符串作为参数,将会根据该字符串去拆分数组(如果传入一个空串,则将每个字符都拆分为数组中的一个元素)
10)toUpperCase()
将一个字符串全部字符转换成大写后返回
11)toLowerCase()
将一个字符串全部字符转换成小写后返回
补充 this
当解析器/浏览器在调用函数时,每次都会向函数内部传递进一个隐含的参数,这个隐含的参数就是this,this指向的是一个对象
- 这个对象我们称为函数执行的 上下文对象
- 根据函数的调用方式不同,this会指向不同的对象
- 以函数的形式调用时,this永远都指向window对象
- 以方法的形式调用时,this指向调用方法的那个对象
- 当以构造函数的形式调用时,this指向的就是构造函数那个新创建的对象
- 使用call和apply调用时,this是那个指定的对象
补充 toString()“重写”
我们发现当我们直接对alert函数内传入一个对象的时候,常常输出的是“[object Object]”
- 实际上这是输出了对象的toString()方法的返回值
- 如果我们希望在输出对象时不输出“[object Object]”,可以为对象添加一个toString方法
细心的同学可能会注意到这不就是方法重写吗,实际上在js中并没有重写这个概念
补充 垃圾回收机制(GC)
-
实际上就像人的生活时间长了会产生垃圾一样,程序在运行过程中也会产生垃圾,这些垃圾积攒过多之后,会导致程序运行的速度变慢,因此我们需要一个垃圾回收的机制,来处理程序运行过程中产生的垃圾
首先我们需要明确在程序中,什么样的“东西”才算是垃圾
- 当一个对象没有任何的变量/属性对它进行引用,此时我们将永远无法操作该对象,这种对象就是垃圾,这种对象过多就会占用大量的内存空间,导致程序运行变慢
事实上,JS中拥有自动的垃圾回收机制,会自动的将这些垃圾对象从内存中销毁,我们不需要也不能进行垃圾回收的操作,我们需要做的只是将不再使用的对象设置为null即可
九、js中的事件
什么是事件?事件是电脑输入设备与页面进行交互的响应,我们称之为事件
常用的事件如下:
-
onload加载完成事件; 页面加载完成后,常用于做页面js代码初始化操作
-
onclick单击事件; 常用于按钮的点击响应操作
-
onblur失去焦点事件; 常用于输入框失去焦点后验证其输入内容是否合法
-
onchange内容发生改变事件; 常用于下拉列表与输入框内容发生改变后操作
-
onsubmit表单提交事件; 常用于表单提交前,验证所有表单项是否合法
1.事件的注册(绑定)
什么是事件的注册(绑定)?
其实就是告诉浏览器,当事件响应后要执行哪些操作代码,叫事件注册或事件绑定
而事件的注册(绑定)又分为静态注册或动态注册
1.1事件的静态注册
- 通过HTML标签的事件属性直接赋予事件响应后的代码,这种方式我们叫静态注册
1.2事件的动态注册
- 是指先通过js代码得到标签的dom对象,然后再通过 对象.事件名 = function(){}
这种形式赋予事件响应后的代码,叫动态注册
动态注册的基本步骤如下:
1)获取标签对象
2)标签对象.对象名=function(){}
2. onload事件
- onload通常写在body标签上
3. onclick事件
4. onblur事件
效果如下:
5.onchange事件
6.onsubmit事件
值得注意的是在静态注册时我们在调用函数前需要有一个return
补充 7. 滚轮事件
滚轮事件 :根据MDN上给出的解释就是:当滚动鼠标滚轮或操作其它类似输入设备时会触发滚轮事件。
也就是说滚轮事件不单单指鼠标滚轮,也包括其他可以出发滚轮事件的设备,比如说电脑触控板等都是可以触发滚轮事件的。所以这里的标题实际不是特别的正确,不应该只界定在鼠标触发的滚轮事件。
在MDN上同样还有对滚轮事件的注意事项:
- 滚轮方向的改动不一定就完全是文档内容的滚动方向,因为标准并未定义滚轮事件会引发什么样的行为,滚轮事件引发的行为都是由浏览器平台来自己定义的。
- 即便滚轮事件引发了文档内容的滚动行为,也不表示滚轮方向和文档内容的滚动方向一定相同。因而通过该滚轮事件获知文档内容滚动方向的方法并不可靠。
- 如果想要获取可靠的文档内容的滚动方向,可以通过对文档内容的滚动事件添加监听,主要监听scrollLeft和scrollTop两个值的变化,来推断出滚动方向
滚轮事件的可使用场景(个人见解)
- 图片放大预览时的放缩操作
- 内容懒加载时,监听滚动条的滚动位置
- blog中,显示当前文章内容滑动到什么标题下
- 商品侧边分类显示商品整体滑动位置(描述的有点怪,具体可参考美团外卖)
- 歌词的滑动,监听滑动位置,显示对应歌词的加粗
- 列表展示数据时,默认分页操作,可以根据滑动高度是否到底触发再次请求的操作,模拟分页。(聊天框的实现也是类似的,比如微信)
- 暂时没想到了,以后再有的话继续补充.....
不同浏览器平台滚轮事件的差异:
滚轮事件主要有三种类型,分别是: wheel(各个厂商的高版本浏览器都支持)
, mousewheel(不支持火狐)
, DOMMouseScroll(该事件需要使用addEventListener绑定,可以特别地支持低版本火狐)
虽然在MDN中也有说mousewheel
已经被弃用了,主要使用的是wheel
,但是为了兼容低版本浏览器,还是需要添加上去。在低版本的firefox浏览器中,其也不支持wheel
,所以还需要添加DOMMouseScroll
已达到兼容的目的。
同时,各个不同的浏览器平台对于滚轮事件的上下移动的判定使用的参数是完全不同的,有的是使用wheelDelta,有的则是使用detail的,并通过他们的正负来判断其滚动的方向的,下面是我在网上找到的一张图,可以从这张图上看出当前使用比较多的浏览器上是通过什么判断的(可忽略IE)
从上表可以看出,deltaY一个可以判断滚轮的方向是向上还是向下(正值向下滚动,负值向上滚动)。这里的H取得是
document.body.clientHeight || window.innerHeight
。n是滚动的行数。一般来说n默认的是3行。通过分析可以得出以下结论:
- 可靠属性:deltaY,方向判断方法一致(正值向下滚动,负值向上滚动),与操作系统鼠标设置有关联,但需注意绝对值算法不统一。(比如在mac上使用的时候,如果选择的鼠标滚轮是自然属性,实际就是向上滚动是正值,向下滚动是负值)
- 功能方面:Firefox能直观反映滚动行数,但不能直观与浏览器默认滚动条保持同步;其他几组浏览器则恰好相反;
- 个人建议:个人认为wheelDelta的最初设计思想很好,电脑鼠标滚轮垂直行数默认值是3,wheelDelta默认值120,即单行行高40px,即使用户电脑做了个性化设置,像素值也不会出现循环小数,避免了Chrome的deltaY设计缺陷,有利于行业规范化,所以建议各浏览器厂商能完整支持wheelDelta这一属性。
上述有关滚轮事件的笔记来自鼠标滚轮事件的学习与总结 - 掘金 (juejin.cn)
补充:当滚轮滚动时,如果浏览器有滚动条,我们滚动事件触发时可能导致滚动条随之滚动,这是浏览器的默认行为,如果不希望发生,则需要取消默认行为
- return false
- 使用addEventListener()绑定的事件需要使用 事件对象.preventDefault() 来取消默认行为
补充 onscroll Vs onwheel
onwheel 事件在鼠标滚轮在元素上下滚动时触发。同样可以在触摸板上滚动或放大缩小区域时触发(如笔记本上的触摸板)。
onscroll 事件在元素滚动条在滚动时触发。滚动条必须存在,否则不会触发。无论以那种方式,只要滚动条滚动,事件都会触发。触发方式:鼠标滚轮,鼠标拖动,键盘上下键,或者设置的滚动函数,如 scrollTo,scrollBy,scrollByLines, scrollByPages。
当鼠标滚轮滚动时,onwheel事件先被触发,若滚动条滚动,则onscroll事件会相继被触发。
补充 8.键盘事件
键盘事件主要有:onkeydown(按键被按下),onkeyup(按键被松开)
此外,键盘事件一般都会绑定给一些可以获取到焦点的对象(例如文本框)或者是document
对于onkeydown来说:
- 如果一直按着一个键不放,则事件会一直触发
- 当onkeydown事件连续触发时,第一次和第二次之间的时间间隔会稍微长一点,其他时候会非常快,这种设计是为了防止误操作
一般来说我们使用键盘事件重点是获取用户按下的是什么
可以通过keyCode()来获取按键的编码(可能未来会过时)
这里还有使用 事件对象.key来得到按下的内容
除此之外,键盘事件对象还提供了几个属性:
- altkey
- ctrlkey
- shiftkey
这些属性可以用来判断alt、ctrl、shift是否被按下,如果按下则返回true,反之返回false
值得注意的是,在文本框中输入内容,属于onkeydown的默认行为,如果取消了默认行为,则在文本框中不会出现输入的内容
补充 事件的冒泡
所谓的事件的冒泡 指的就是事件的向上传导,当后代元素上的事件被触发时,其祖先上元素的相同事件也会被触发
在开发中,大部分情况下冒泡都是有用的,如果不希望发生冒泡可以通过事件对象来取消冒泡
补充 事件的委派(冒泡的应用)
有时候我们会希望为多个对象绑定同一种事件,前面的学习中,我们使用了for循环+this绑定事件 的方法来实现,但是这样会存在一个问题:该方法只能为已有的对象绑定事件,而新添加的对象必须重新绑定
我们希望的是:只绑定一次事件,即可应用到多个元素上,即使元素是后添加的
这里我们可以 将其事件绑定给元素的共同祖先元素
这里我们便得到了事件的委托的概念:
- 指将事件统一绑定给元素共同的祖先元素,这样当后代元素上的事件触发时,会一直冒泡到祖先元素,从而将祖先元素的响应函数来处理了事件
- 事件委派是利用了事件冒泡,通过委派可以减少事件绑定的次数,提高程序的性能
不过事件委派也会带来一定的问题,例如可能导致当 触发事件的对象 与 我们期望的 不同时也会得到响应,此时我们需要在 响应函数内 对 触发事件的对象 进行判断(利用 事件对象中的target属性,它表示触发事件的对象)
补充 事件的传播
事件的传播分为三个阶段:
- 捕获阶段
- 在捕获阶段时从最外层的祖先元素,向目标元素进行事件的捕获,但是默认此时不会触发事件
- 目标阶段
- 事件捕获到目标元素,捕获结束开始在目标元素上触发事件
- 冒泡阶段
- 事件从目标元素向它的祖先元素传递,依次触发祖先元素上的事件
如果希望在捕获阶段就触发事件,可以将addEventListener()的第三个参数设置为true(则不会在冒泡阶段触发事件了)
一般情况下我们不会希望在捕获阶段触发事件,所以这个参数一般都是false
十、DOM模型
DOM全称 Document Object Model 文档对象模型
提示:Document 对象是 Window 对象的一部分,可通过 window.document 属性对其进行访问
- [window对象]
- 它是一个顶层对象,而不是另一个对象的属性,即浏览器的窗口
- [document 对象]
- 该对象是window和frames对象的一个属性,是显示于窗口或框架内的一个文档
- 文档——表示的就是整个html网页页面
- 对象——表示将网页中的每一个部分都转换为了一个对象
- 模型——表示对象之间的关系,这样分别我们获取对象
JS中通过DOM来对HTML文档进行操作,只要理解了DOM就可以随心所欲地操作WEB页面
简而言之就是 我们把文档(即一个html页面)中的标签、属性、文本转换成对象来管理
补充 节点
Document对象及其方法/属性(document表示整个html页面)
理解:
- Document管理了所有的html文档内容
- Document是一种树状结构的文档,有层级关系
- 它让我们把所有的标签都 对象化
- 我们可以通过Document访问所有的标签对象
例如下面这个例子演示对象化:
1.getElementById方法
我们直接通过案例进行演示:
需求:当用户点击“校验”按钮时,获取输入框的内容,验证其是否合法(规则是由字母或数字或下划线组成且长度为5—12)
这里使用到了正则表达式(可参考JavaScript RegExp 对象)
下面是另外两种常见的验证提示
注意这里的innerHTML是我们所得到的dom对象的一个属性,该属性可读可写
2.getElementsByName方法
依旧直接看案例
我们这里希望实现如下三个功能:
这里的我们得到的dom对象同样是可以操作对应标签的属性如checked属性
且得到的集合的操作与数组一样,集合中都是dom对象,元素顺序是它们在html页面中从上到下的顺序
3.getElementsByTagName方法
该方法与方法3大同小异,只是该方法利用的是标签名来确定得到哪些dom对象而已
.三种查询方法的使用注意事项
补充getElementsByClassName()
补充querySelector()
补充querySelectorAll()
4. createElement方法与appendChild方法
下面我们进行演示(需求是使用js为body标签添加一个子节点div标签)
注意:这里我们必须使用window.οnlοad=function(){}保证里面代码在页面加载完成后再执行,否则会出现body对象为空的错误
获取元素节点的子节点
注意分辨是 属性 还是 方法
如果获取的是子节点的话,DOM标签与标签间空白也会被当做 节点
这里补充一下以下几个属性:
- children属性可以获取当前元素的所有子元素(注意这里是子元素而不是节点了)
- firstElementChild属性可以获取当前元素的第一个元素
- lastElementChild属性可以获取当前元素的最后一个元素
获取元素节点的父节点和兄弟节点
这里也是补充以下这些属性:
- previousElementSibling获取前一个兄弟元素(不包括空白文本)
- nextElementSibling获取下一个兄弟元素(不包括空白文本)
补充 document的常用属性
1)body
2)documentElement
DOM的增删改
1)createElement()
2) createTextNode()
3) appendChild()
4) insertBefore()
5) replaceChild()
6) removeChild()
事实上,如果使用innerHTML属性也可以 实现DOM的增删改,但一般我们都是两种方式结合着使用,例如在添加文本时直接使用innerText即可
补充 for循环与事件绑定带来的问题
如图我们希望为allA数组中的每一个元素绑定一个单击事件,这时会带来一个时间差的问题
我们的单击事件是在用户单击时才会执行,但是for循环在页面加载完成后就会执行了,则for循环的循环变量 i ,在单击事件响应时则已经变成了allA.length,所以我们此时在事件中最好不要再使用该循环变量,而是使用this
前面我们都是利用DOM来操作文档中的元素/标签,下面我们介绍如何
使用DOM来操作样式:
1)修改/查询内联样式
注意这里的 样式值 应该是一个 字符串类型
注意,上述通过style属性操作的样式 只是 内联样式,对样式表中的样式无效
2)获取元素当前的样式(查询)
该方法只能用于查询对象,而不能修改对象
该方法有个值得注意的问题是:
如上:为什么我们不是直接return一个getComputedStyle(obj , null).name而是使用【name】呢?
因为如果.name此时形参传入的值是一个字符串的话,那就写死了,因为name是变量,使用中括号会更为严谨,不用如果,name可能就是关键字,有些元素有name这个属性,.name肯定读name这个属性
下面介绍其他样式相关的属性:
1)clientWidth和clientHeight
2) offsetWidth和offsetHeight
3)offsetParent()
- 可以用来获取当前元素的定位父元素
- 会获取到离当前元素最近的开启了定位的祖先元素(如果所有的祖先元素都没有开启定位,则返回body)
4)offsetLeft()与offsetTop()
- offsetLeft()获取当前元素相对于其 定位父元素 的水平偏移量
- offsetTop()获取当前元素相对于其 定位父元素 的垂直偏移量
值得注意的是该两个属性都是 只读的 !
5)scrollWidth()与scrollHeight()
它们可以获取元素整个滚动区域的宽度与高度
6) scrollLeft()与scrollTop()
它们可以分别获取 水平滚动条滚动的距离 和 垂直滚动条滚动的距离
值得注意的两个条件是:
- 当满足scrollHeight(整个滚动页面高度)-scrollTop(滚动条滚动上下距离) == clientHeight(当前可见高度) 时,说明滚动条滚动到底了
- 当满足scrollWidth(整个滚动页面宽度)- scrollLeft(滚动条滚动左右距离) == clientWidth(当前可见距离)时,说明滚动条滚动到底了
但是,在实际使用中,我发现 使用 scrollTop 属性时存在问题:精度问题
即scrollTop的返回值经常是一个小数,此时会导致我们上面在判断是否滚动到底时等式无法成立
具体原因读者可参考 为什么Chrome的document.body.scrollTop可以是小数 - JoyNop
最后的解决方案是使用式子
box1obj.scrollHeight-box1obj.scrollTop-box1obj.clientHeight<1
代替原来的等式
十一、BOM(浏览器对象模型)
11.1 基本介绍
BOM直译过来即 浏览器对象模型,我们前面所学的DOM可以使我们通过JS来操作文档/网页,所以DOM叫做文档对象模型,而BOM可以使我们通过JS来操作浏览器,所以叫做浏览器对象模型
在BOM中,它为我们提供了一组对象,用来完成对浏览器的操作
11.2 BOM对象概况
1)Window
- 代表的是整个浏览器的窗口,同时window也是网页中的全局对象
2)Navigator
- 代表当前浏览器的信息,通过该对象可以识别不同的浏览器
3)Location
- 代表当前浏览器的地址栏信息,通过Location可以获取地址栏信息,或者操作浏览器跳转页面
4)History
- 代表浏览器的历史记录,可以通过该对象来操作浏览器的历史记录(由于隐私原因,该对象不能获取到具体的历史记录,只能操作浏览器向前或者向后翻页,并且该操作只在当次有效)
5)Screen
- 代表用户的屏幕信息,通过该对象可以获取到用户的显示器的相关信息
注意: 这些BOM对象在浏览器中都是作为window对象的属性保存的,因此可以通过window.BOM对象 来使用它们(由于window是全局的,也可以省略window)
11.1 Navigator
由于历史原因,Navigator对象中的大部分属性都已经不能帮助我们识别浏览器了
- 一般我们会使用userAgent属性来判断浏览器的信息
userAgent是一个字符串,这个字符串中包含有用来描述浏览器信息的内容,即不同的浏览器会有不同的userAgent
- 如果通过userAgent不能判断,还可以通过一些浏览器中特有的对象,来判断浏览器的信息
11.2 History
常用属性/方法如下:
1)length
- 可以获取到当前访问的链接数量
2)back()
- 可以用来回退到上一个页面,作用和浏览器的回退按钮一样
3)forward()
- 可以用来跳转到下一个页面,作用和浏览器的前进按钮一样
4)go()
它可以用来跳转到指定的页面
它需要一个整数作为参数
- 1:表示向前跳转一个页面,相当于一个forward
- 2:表示向前跳转两个页面
- -1:表示向后跳转一个页面,相当于一个back
- -2:表示向后跳转两个页面
11.3 Location
该对象中封装了浏览器的地址栏信息
- 如果我们直接打印location,则可以获取到地址栏的信息(当前页面的完整的路径)
- 如果我们直接将location属性修改为一个完整的路径/相对路径,则我们页面会自动跳转到,且会生成相应的历史记录
常用方法如下:
1)assign()
用来跳转到其他的页面,作用和直接修改location的值一样
2)reload()
用于重新加载当前页面,作用与刷新按钮一样
如果在方法中传递一个true,作为参数,则会强制清空缓存刷新
3)replace()
可以使用一个新的页面替换当前页面,调用完毕后也会跳转页面,但是与assign()和直接修改location的值 不同的是,replace()不会生成历史记录,即不能回退
十二、定时器
常用方法如下:
1)setInterval()(定时调用)
- 使用该方法可以实现 定时调用 的功能
- 可以将一个函数,每个一段时间调用一次
参数:
- 回调函数,该函数会每隔一段时间调用一次
- 每次调用间隔的时间,单位是毫秒
返回值:
- 返回一个Number类型的数据
- 该返回值是定时器的唯一标识(例如在一段代码中,我们开启了两个定时器,则它们的返回值分别是1、2,即它们被称为“1号定时器”、“2号定时器”)
值得注意的问题是:
我们在给一个元素开启定时器之前,需要将当前元素上的其他定时器关闭
2)clearInterval()
- 该方法可以用来关闭一个定时器,方法中需要一个定时器的标识作为参数,这样将关闭标识数 对应的定时器
- clearInterval()可以接收任意的参数,如果参数是一个有效的定时器的标识,则停止对应的定时器,反之什么都不做
3)setTimeout()(延时调用)
- 该方法的用法参考setInterval的用法
- 延时调用一个函数不会立马执行,而是隔一段时间之后再执行,而且只会执行一次
4)clearTimeout()
- 该方法与clearInterval相似,与setTimeout是一对
- 可以使用该方法来关闭一个延时调用
十三、类的操作
1. 修改元素样式
前面的学习中,我们学习了在JS代码中,使用style属性修改元素的样式
实际上,利用这种方式修改元素的样式,代码执行的性能是比较差的,因为每当我们修改一个样式,浏览器就需要重新渲染一次页面,而且这种形式在我们需要修改多个样式时也不太方便
我们可以通过修改元素的class属性来间接地修改样式
- 我们可以只修改一次,即可同时修改多个样式
- 浏览器只需要重新渲染一次,性能较好
- 而且该种方式可以使表现(CSS)与行为(JS)进一步地分离
十四、JSON
我们在进行开发的时候,会有前端与后端交互的需求,此时JS如果需要将某些对象传递给服务器,就会出现语言识别的问题
- JS的对象只有JS自己认识,其他的语言都不认识
- JSON就是一个特殊格式的字符串,这个字符串可以被任意的语言所识别,并且可以转换为任意语言的对象,JSON在开发中主要用于数据的交互
JSON全称JavaScript Object Notation即JavaScript对象表示法
JSON和JS对象的格式一样,只不过JSON字符串中的属性名必须加双引号,其他的和JS语法一致
JSON分类:
- 对象{}
- 数组[]
JSON中允许的值:
- 字符串
- 数值
- 布尔值
- 数组
- 对象(非函数对象)
- null
将JSON字符串转换为JS中的对象
在JS中,JS为我们提供了一个工具类JSON
这个工具类可以帮助我们将一个JSON转换为JS对象,也可以将一个JS对象转换为JSON
JSON工具类常用属性及方法:
1)JSON.parse()
- 它可以将一个JSON字符串转化为js对象
- 它需要一个JSON字符串作为参数,会将该字符串转换为js对象后返回
2)JSON.stringify()
- 可以将一个JS对象转换为JSON对象
- 需要一个JS对象作为参数,会返回一个JSON对象