基础阶段
变量数据类型
js 中,变量的数据类型是在程序运行时确定下来的,如下
var age = 10 // 数值型
var areYouOk = 'Yes' // 字符串类型
同时,js 中的变量数据类型,能够根据等号右边的值来变化,如下
var x = 10 // 数值型
x = 'hello' // 字符串型
简单数据类型
js 中的数据类型可以分为两大类,首先是简单数据类型Number String Boolean Undefined Null,如下
| 简单数据类型 | 说明 | 默认值 |
|---|---|---|
| Number | 数字类型,包含整数型和浮点型,如 21,22.3,其中Infinity -Infinity分别代表正无穷和负无穷,NAN代表非数字,可以使用isNaN()判断非数字 | 0 |
| Boolean | 布尔值类型,如 true false,等价于 1,0 | false |
| String | 字符串类型,如'张三',js 中字符串都带引号 | '' |
| Undefined | var a;声明了变量a,但a没有给值,此时a == undefined | undefined |
| Null | var a= null;声明了变量a为空值 | null |
数组创建方式
var arr = new Array(2) // 使用 new 创建数组,数值参数可以指定数组的长度
var arr = [] // 使用字面量创建数组
函数
申明函数
function 函数名(){
// 函数体
}
函数不调用,不会执行,使用
函数名()来调用
函数的参数有如下形式
function fun(形参1,形参2...){ // 函数神秘时,传入的为形参
}
fun(实参1,实参2...) // 函数调用时传入的是实参
在 js 中,如果实参的个数多余形参的个数,多余的实参会被忽略,而不会产生错误,如下
function fun(a,b){ // 两个形参
}
fun(1,2,3) // 实际调用时,传入了 3 个参数,多余的第三个参数会被忽略
如果实参的个数少于形参的个数,那么缺少的形参在函数内被调用时,会被复制为undefined【可以将形参理解为未被申明的变量】
js 中函数的返回值只能有一个,如下
function fun(){
return 1,2 // 如果返回值有多个,则返回最后一个值:2
}
如果函数没有规定返回值,则返回undefined
JavaScript 作用域
作业域指的是代码起作用的范围,在es6之前,js 中的作用域分为
- 全局作用域:整个
<script>标签,或则是一个单独的 js 文件 - 局部作用域:只要在函数内部申明,就为局部作用域(包括函数的形参,也可以看作局部变量)
js 中没有块级作用域,但在es6中新增了块级作用域
全局变量和局部变量
- 全局变量:全局作用域下的变量,在全局作用域下都可以使用,全局变量在浏览器退出时被销毁
- 局部变量:局部作用域下的变量,局部变量在程序执行完毕后会销毁
如下代码
var x = 10 // 全局变量,申明在全局作用域内
function fun() {
var n1 = 10 // 局部变量,申明在局部作用域内
n2 = 20 // 注意:函数内部,没有申明,直接赋值的变量,也属于全局变量
console.log('--',n1, n2)
}
fun()
console.log(x) // 全局变量,可以获取到
console.log(n2) // 全局变量,可以获取到
console.log(n1) // 局部变量,报错
块级作用域
在 js 中没有块级作用域{} for{} if{},如下代码
if (1 > 2) {
var num = 10
}
console.log(num) // 在 if 代码块中申明的变量,if 代码块之外也能获取到
在
es6中新增了块级作用域
作用域链
内部函数分为外部函数的变量,采取的是链式查找的方式来决定取那个值,也就是就近原则,也就是在当前作用域中如果有该变量,则直接使用,否则逐层向上查找
JavaScript预解析
js 代码在运行时,分为预解析和代码执行,其中代码执行就是直接按照代码的书写顺序从上到下执行,而预解析又分为变量预解析和函数预解析
变量预解析
变量预解析也叫变量提升,指的是将所有的变量申明提升到当前作用域的最前面,但变量的赋值不会被提升,如下代码
// 预解析之前
console.log(num1)
var num = 10
// 预解析之后,变量被提升了
var num
console.log(num)
num = 10
这就会导致
console的结果为undefined,因为num1变量虽然被提升到最前面了,但是它的赋值语句却没有被提升
函数预解析
函数预解析也叫函数提升,将所有的函数申明提升到当前作用域的最前面,不调用函数,如下代码
// 预解析之前
fun() // 函数调用
function fun(){ // 函数申明
console.log('hello')
}
// 预解析之后,函数被提升了
function fun(){
console.log('hello')
}
fun()
如果是单纯的函数声明,不论是在函数声明的前或后调用,在预解析之后,都能成功执行
但如果将函数声明赋值给了变量,如var fun = function fun() { console.log('hello') },那么就会遵循变量提升的的逻辑,此时在函数声明之前调用,就会报错,因为变量提升只会提升变量声明,如下代码
// 预解析之前
fun() // 函数调用
var fun = function fun(){ // 将函数的声明赋值给 fun 变量,通过 fun() 形式调用函数
console.log('hello')
}
// 预解析之后
var fun // 变量提升
fun() // 函数变量调用,但因为变量提升,此时的函数变量并没有被赋值,为 undefined
fun = function fun(){ // 最后才对 fun 进行赋值,在这之前调用都会报错
console.log('hello')
}
JavaScript 对象
在 js 中,对象是一组无序的相关属性的方法和集合,所有的事务都是对象,例如字符串数值和数组,函数等
创建对象的三种方式
使用字面量创建对象{ },如下代码
var obj = {
name:'Lucy',
age:18,
sex:'man',
sayHi:function hi(){
console.log('hi')
}
}
要调用对象中的属性,除了可以通过
对象.属性名的方式,还可以通过对象名['属性名'],如果属性为函数类型,则直接通过对象名['属性名']()的形式来调用
使用new创建对象,如下代码
var obj = new Object()
obj.name = 'Lucy'
obj.age = 18
obj.sayHi = () => {
console.log('hi')
}
使用构造函数创建对象,字面量和new关键字创建对象的方式,每次只能创建一个对象,而构造函数能够将创建对象的行为封装起来,如下代码
function User(name, age, active) { // 构造函数函数名的首字母必定是大写的,可以传入参数
this.name = name // 函数内部的属性必须使用 this 来标识
this.age = age
this.sayHi = active
// 构造函数不需要 return 就可以返回结果
}
let user = new User('Lucy', 18, () => { // 使用 new 关键字调用构造函数来创建对象
console.log('hi')
})
使用
new关键字创建一个对象时,有如下过程
- 在内存中创建一个新的空对象
- 让
this指向这个对象- 执行构造函数中的代码,给这个对象添加新的属性和方法
- 返回这个新的对象(不需要
return)
变量对象中的属性
使用forin循环可以遍历对象中的属性,如下代码
function User(name, age, active) {
this.name = name
this.age = age
this.sayHi = active
}
let user = new User('Lucy', 18, () => {
console.log('hi')
})
for (let userKey in user) { // 拿到的 userKey 为对象的属性名
console.log(user['userKey']) // 取到对象的属性
}
内置对象
js 中的对象分为3种:自定义对象 内置对象 浏览器对象,自定义和内置对象为 js 的基础内容,而浏览器对象又为 js 独有,相关文档可以在 MDN Web Docs (mozilla.org) 中查询
Math 对象取整
Math.floor(1.1) // 向下取整,往小了取值
Math.ceil(1.1) // 向上取整,往大了取值
Math.trunc(1.1) // 取整数部分
Math.round(1.5) // 四舍五入(特殊情况,如果小数为 .5,则直接向上取整)
Math 随机数
得到[0,1)的随机数值
Math.random() // 函数返回一个浮点数, 伪随机数在范围从 0 到小于 1,也就是说,从 0(包括 0)往上,但是不包括 1(排除 1)
得到指定值之间的随机数。这个值不小于 min(有可能等于),并且小于(不等于)max
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
得到指定值之间的随机整数。这个值不小于 min (如果 min 不是整数,则不小于 min 的向上取整数),且小于(不等于)max
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min; // 不含最大值,含最小值
}
得到指定值之间的随机整数,包含两个值
function getRandomIntInclusive(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min; // 含最大值,含最小值
}
检测是否为数组
console.log([] instanceof Array) // 方式 1
console.log(Array.isArray([])); // 方式 2
数组常用 api
var arr = []
arr.push('a')const arr = [];
// 添加元素
arr.push('a') // 直接在数组末尾添加元素
arr.push('b', 'c') // 一次性添加多个元素
arr.push(...arr) // 直接添加数组
arr.unshift('d') // 在数组开头添加元素
// 删除元素
arr.pop() // 移除数组末尾的元素
arr.shift() // 移除数组开头的元素
// 查找元素
arr.indexOf('a') // 查找元素首次出现的索引,没有返回 -1
arr.lastIndexOf('a') // 查找元素最后出现的索引,没有返回 -1
// 数组转换为字符串
console.log(arr.toString()); // 逗号分隔的字符串
console.log(arr.join('|')) // 指定分隔符的字符串
// 其他
let newArr = arr.concat(['e', 'f', 'g']) // 连接两个或多个数组,返回新数组
let sliceArr = newArr.slice(0, 2) // 截取指定区间的数组(左闭右开),返回新数组
/*
删除指定索引开始,指定个数的元素(计数包括开始元素,例如从 0 索引开始删除,则再往后删除 1 个,即代表删除 2 个元素)
原数组会被影响,返回值为被删除的元素集合
*/
let spliceArr = newArr.splice(0, 2)
console.log(arr)
console.log(newArr)
console.log(sliceArr)
console.log(spliceArr)
console.log(newArr)
基本包装类型
为了方便操作基本数据类型,js 提供了三个特殊的引用类型String Number Boolean,相当于将简单数据类型包装为了复杂数据类型,使其拥有了属性和方法
字符串不可变
js 中的字符串不可变,如下代码
var str = 'abc'
/*
当 str 被重新赋值时,常量 'abc' 不会被修改,依然在内存中,重新赋值会重新开辟内存空间,这就是不可变的特点
*/
str = 'hello'
简单数据类型&引用数据类型
简单数据类型又叫值类型,即变量中存储的是值本身,如string number boolean undefined null
引用类型又叫复杂数据类型,即变量中存储的是地址值,如通过new关键字创建出的对象等Object Array Date
简单数据类型存储于栈中,复杂数据类型存储在堆中
简单数据类型在传参时,将数据的值复制一份给参数变量,参数变量的值被修改,不会影响到外部变量;引用类型反之,引用类型将变量的地址引用赋值给参数变量,参数变量修改会影响外部变量
WebApi
DOM
DOM:文档对象模型(Document Object Model,简称 DOM),是 W3c 组织推出的处理
HTML的标准编程接口,通过这些接口,能够以编程的方式改变的网页的结构,内容和样式
DOM 树
- 文档,一个页面就是一个文档,DOM 中使用
document表示 - 元素,页面中的所有标签都是元素,DOM 使用
element表示 - 节点,网页中的所有内容都是节点(标签,属性,文本,注释等),DOM 使用
node表示
整个可以看作一个有层次的树形结构,因此又称为DOM 树
获取页面元素
获取页面元素有一下几种方式
-
根据 id 获取,如下代码
<div id='box'> </div> <script> document.getElementById('box') </script> -
根据标签名进行获取
<div></div> <div></div> <div></div> <div></div> <script> document.getElementsByTagName('div') </script>还可以使用先获取父元素,再获取子元素的的形式
<div> <span></span> <span></span> <span></span> </div> <script> let fatherEle = document.getElementsByTagName('div') // 返回的是一个伪数组形式,哪怕页面中只有一个 div fatherEle[0].getElementsByTagName('span') // 只有一个父元素,直接通过 0 索引获取 </script> -
通过 HTML5 新增的方式获取
-
根据类名获取元素,这是 html5 新增的方式,这种方式在
ie7-8中不被支持,如下代码<div id="box"> <span class="box-span"></span> <span class="box-span"></span> <span class="box-span"></span> <span class="box-span"></span> </div> <script> document.getElementsByClassName('box-span') // 返回伪数组 </script> -
返回指定选择器的第一个元素,如下代码
/* 该选择器不区分 className 或者 id 亦或是元素名,都可以作为参数传入 */ document.querySelector('.box-span') // 选择指定类名 document.querySelector('#box') // 选择指定ID document.querySelectorAll('.box-span') // 选择所有,返回伪数组
-
-
特殊元素获取
let body = document.body // 获取当前页面的 body 元素 let html = document.documentElement // 获取当前页面的 html 元素
事件
事件是一种触发响应的机制,网页中的每个元素都可以触发一些 js 事件
事件有三部分组成,也称之为事件三要素
-
事件源
事件被触发的对象,也就是说谁触发了这个事件,可以是一个按钮,也可以是一个 div
-
事件类型
该事件是如何触发的,如鼠标点击,鼠标经过等
-
事件处理程序
事件被触发后,需要执行的逻辑
代码演示如下
<!--button:事件源-->
<button>
click
</button>
<script>
// onclick:事件类型,点击事件
document.querySelector('button').onclick =
// 事件处理程序,点击 button 后弹出 Hello
() => {
alert('Hello')
}
</script>
常见的鼠标事件
| 鼠标事件 | 触发条件 |
|---|---|
| onclick | 鼠标左键点击触发 |
| onmouseover | 鼠标经过触发 |
| onmouseout | 鼠标移开触发 |
| onfocus | 获得鼠标焦点触发 |
| onblur | 失去鼠标焦点触发 |
| onmousemove | 鼠标移动触发 |
| onmouseup | 鼠标按下释放鼠标时触发 |
| onmousedown | 鼠标按下触发 |
注册事件
给元素添加事件,称之为注册事件或者绑定事件,注册事件有两种方式:传统方式和方法监听方式
传统方式的事件注册具有唯一性,如onclick,同一个元素同一事件只能注册一个处理函数,最后设置的处理函数会覆盖之前的处理函数,如下
document.querySelector('div').onclick = () => {
console.log('hi')
}
// 该事件会覆盖上面的事件,最终结果为打印 hello,以此类推
document.querySelector('div').onclick = () => {
console.log('hello')
}
除了传统的事件注册,还有一种方法监听的注册方式,这也是 w3c 标准推荐的一种方式,如下代码(ie9 之前不支持)
document.querySelector('div').addEventListener('click', () => {
console.log('hi')
})
/*
使用注册事件的方式,可以为一个元素同一个事件类型注册多个事件处理逻辑且不会被覆盖,多个处理逻辑会一起生效
最终的打印结果为:hi hello
*/
document.querySelector('div').addEventListener('click', () => {
console.log('hello')
})
移除事件
let divEle = document.querySelector('div')
divEle.addEventListener('click', function fn() {
console.log('hi')
/*
使用 removeEventListener 可以移除元素指定类型指定函数名的事件
*/
divEle.removeEventListener('click', fn)
})
DOM 事件流
事件流描述的是从页面接收事件的顺序,事件发生时会在元素节点之间按照特定的顺序传播,这个传播的过程即事件流,当我们给页面中的一个 div 注册了点击事件,该事件触发时,会经过三个阶段,分别为捕获阶段 当前目标阶段 冒泡阶段,其中捕获阶段并不是直接就找到了当前被点击的 div,而是先从Document元素开始找起,依次经过html body后,最终找到事件的触发对象:div,这称之为目标阶段,目标阶段会执行事件的处理逻辑
目标阶段完成后,从事件触发对象开始,又会依次往上根据dom层级传递该事件,最终到达Document后,整个事件流就结束了
当触发一个事件时,并不只是事件的目标对象会接收到该事件,目标对象对应层级的所有dom元素都会接收到该事件,这就相当于往水里面扔一块石头,石头沉入水底的过程,称之为事件捕获,沉入水底的目标位置,称之为事件的触发对象,沉入水底后往上冒出的水泡,称之为事件冒泡
代码演示如下
注意
- js 代码只能执行捕获或冒泡其中一个阶段
- onclick 和 attachEvent 只能得到冒泡阶段
- addEventListener(type, linstener, [,useCapture]) 第三个参数如果为 true,则代表在事件捕获阶段调用事件的处理程序,如果为 false,则代表在事件的冒泡阶段调用事件的处理程序(默认为 false)
<div class="father">
<div class="son"></div>
</div>
<script>
document.querySelector('.son').addEventListener('click', () => {
console.log('son')
}, true)
// 因为事件捕获的存在,父元素 .father 首先会执行事件,然后才是子元素执行
document.querySelector('.father').addEventListener('click', () => {
console.log('father')
}, true)
</script>
打印结果:先
father后son
事件冒泡的形式
<div class="father">
<div class="son"></div>
</div>
<script>
document.querySelector('.son').addEventListener('click', () => {
console.log('son')
}, false)
// 因为事件冒泡的存在,子元素 .son 首先会执行事件,然后父元素会在冒泡阶段接收到子元素的事件
document.querySelector('.father').addEventListener('click', () => {
console.log('father')
}, false)
</script>
打印结果:先
son后father
而有些事件是没有冒泡的,例如onblur onfocus onmouseenter onmouseleave等
事件对象
事件对象只有在事件发生时才会存在,如下代码
document.querySelector('div').addEventListener('click', (event) => {
/*
匿名函数中的参数 event 在事件发生时,会被赋值一个事件对象
事件对象中包含的是与事件相关的一些信息,例如鼠标事件就会包含鼠标的坐标信息等
*/
console.log(event)
})
事件对象的常见属性和方法
| 事件对象的属性方法 | 说明 |
|---|---|
| e.target | 返回触发事件的元素 |
| e.type | 返回事件的类型 |
| e.preventDefault | 阻止事件默认行为,如不让表单提交 |
| e.stopPropagation | 阻止冒泡 |
event 事件对象绑定的是触发事件的元素,而 this 返回是绑定事件的元素,例如
ul绑定了点击事件,但我们点击的是里面的li,那么 event 绑定的是li,而ul绑定的是 this
阻止事件默认行为
阻止事件默认行为指的是:让链接不跳转或者让按钮不提交,如下代码
// 对于传统方式,直接 return false 即可阻止事件的默认行为,但 e.preventDefault() 依然可以使用
let aEle = document.getElementById('jump')
aEle.onclick = () => {
return false
}
// 对于事件注册的方式,必须使用 event.preventDefault() 才生效,return false 不生效
let aEle = document.getElementById('jump')
aEle.addEventListener('click', event => {
event.preventDefault()
});
阻止事件冒泡
<div>
<span>click</span>
</div>
<script>
let spanEle = document.querySelector('span')
let divEle = document.querySelector('div')
spanEle.addEventListener('click', e => {
console.log('span')
/*
阻止事件冒泡后,父元素的 click 事件不会被子元素的冒泡所触发
*/
e.stopPropagation()
});
divEle.addEventListener('click', () => {
console.log('div')
})
</script>
事件委托
不用给每个子节点单独的设置事件,而是将事件监听器设置在父元素身上,利用冒泡原理设置影响每个子节点,如下代码
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
/*
为父元素 ul 绑定事件,当点击子元素 li 时,凭借事件冒泡,ul 会接受到子元素事件类型为 click 的事件
因此父元素的 click 事件处理逻辑会被触发,达到为父元素绑定事件处理逻辑,点击子元素触发的效果,而不用
为每一个子元素绑定事件处理逻辑
*/
document.querySelector('ul').addEventListener('click', (e) => {
console.log('hi')
})
</script>
BOM
BOM(Browser Object Model)即浏览器对象模型,提供了独立于内容与浏览器窗口交互的对象,其核心对象为window,BOM是各个厂商在各自的浏览器上自定义的,相较于DOM遵循的W3C规范,兼容性较差
window是浏览器的顶级对象,是 js 访问浏览器窗口的接口;js 定义在全局作用域下的属性和函数,都会变为window对象的属性和方法
window 对象常见事件
窗口加载事件
代码如下
<body>
<script>
/*
load:窗口加载事件,在页面的 dom 内容(包括 css 图片 js 页面元素等)全部加载完毕后会触发该事件
*/
window.addEventListener('load', () => {
document.querySelector('button').addEventListener('click', () => {
alert('click')
})
})
</script>
<button>
click
</button>
</body>
浏览器窗口变化事件
/*
监听浏览器窗口尺寸变化的事件
*/
window.addEventListener('resize', () => {
console.log(window.innerWidth) // 打印当前屏幕宽度
})
定时器
如下代码
<style>
.second-box {
width: 100px;
height: 100px;
margin: 50px auto;
text-align: center;
line-height: 100%;
font-size: 30px;
font-weight: bold;
}
.btn-box {
display: flex;
margin: 0 auto;
width: 100px;
justify-content: center;
}
.btn-box button {
margin-left: 10px;
}
</style>
<body>
<div class="second-box">
60
</div>
<div class="btn-box">
<button class="start">start</button>
<button class="pause">pause</button>
<button class="reset">reset</button>
</div>
<script>
window.addEventListener("load", () => {
let globalClearMark = startSecnod() // 默认开启倒计时
// 开始倒计时
document.querySelector('.start').addEventListener('click', () => {
clearInterval(globalClearMark) // 清除前一个定时器,防止定时器重复开启
globalClearMark = startSecnod() // 新一个定时器后,获取 id,用于下次清除
})
// 暂停倒计时
document.querySelector('.pause').addEventListener('click', () => {
clearInterval(globalClearMark)
})
// 重置倒计时
document.querySelector('.reset').addEventListener('click', () => {
document.querySelector('.second-box').innerHTML = '60'
})
function startSecnod() {
let clearMark
let divEle = document.querySelector('.second-box')
clearMark = setInterval(() => {
let num = Number(divEle.innerText);
if (num === 0) {
clearInterval(clearMark)
return
}
num -= 1
divEle.innerText = num.toString()
}, 1000)
return clearMark
}
})
</script>
This 指向问题
- 一般情况下,this 指向的是调用他的对象
- 全局作用域下或者普通函数中,this 指向全局对象 window(定时器里面的 this 指向的也是 window)
- 构造函数中,this 指向构造函数的实例
- 对象的方法调用中,this 指向对象实例
Js 执行队列
js 是单线程的,同一时间只能做一件事,这是 js 诞生的使命所致,js 是为了操作 dom 元素诞生的,当我们对某个 dom 元素进行增加或删除时,不能同时进行(线程安全问题),因此 js 是单线程的
但为了提升效率,利用多核 cpu 的计算能力,html5 提出了 WebWorker 标准,允许 Js 脚本创建多个线程,于是 Js 中出现了同步和异步
Js 执行机制
Navigator 对象
navigator 包含浏览器有关的信息,例如通过如下代码判断用户设备类型,实现不同网页跳转
if((navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i))) {
window.location.href = 'https://www.baidu.com'; //手机
} else {
window.location.href = 'https://www.jd.com'; //电脑
}
History 对象
使用 history 能够与浏览器历史进行交互,该对象包含用户访问过的 URL,代码如下
history.back() // 后退
history.forward() // 前进
history.go() // 前进或后退,如果为 1,则前进一个页面,如果为 -1 则后退一个页面
获取元素偏移位置
/*
返回元素的偏移位置,如果目标元素为父元素,则以带有定位的父元素为准
,否则以 body 为准
*/
document.querySelector('div').offsetLeft
document.querySelector('div').offsetTop
只读属性,无法通过修改改变元素偏移位置
获取元素大小
/*
获取元素大小,包括 padding 内容区 边框
*/
document.querySelector('div').offsetHeight
document.querySelector('div').offsetWidth
只读属性,无法通过修改改变元素大小
本地存储
- 数据存储在用户浏览器中
- 设置,读取方便,甚至刷新页面不丢失数据
- 容量较大,sessionStorage 约 5m,localStorage 约 20m
- 只能存储字符串
sessionStorage
生命周期为关闭浏览器窗口,同一个页面下数据共享
localStorage
生命周期永久生效,除非手动删除,否则关闭页面也会存在,可以多窗口共享(同一浏览器)