基础语法
数据类型
es6 之前五个基本类型一个引用类型,五个基本类型是null
,undefined
,string
,number
,boolean
,一个引用类型是 object
(包括 object
,function
,array
),
es6 之后新增了 symbol
用来表示独一无二的数,和 bigint
,所以总共是八种数据类型
变量提升
什么是变量提升,举个栗子:
function foo() {
var a = 1
function a() {}
console.log(a) //1
}
foo()
function foo() {
var a = 1
function a() {}
console.log(a) //1
}
foo()
function foo() {
var a
function a() {}
console.log(a) //a()
}
foo()
function foo() {
var a
function a() {}
console.log(a) //a()
}
foo()
上方代码 js 是这样解析的
function foo() {
var a
function a() {}
a = 1
console.log(a) //1
}
foo()
function foo() {
var a
function a() {}
a = 1
console.log(a) //1
}
foo()
function foo() {
var a
function a() {}
console.log(a) //a()
}
foo()
function foo() {
var a
function a() {}
console.log(a) //a()
}
foo()
变量提升的规则: 变量在声明提升的时候,是全部提升到作用域的最前面 变量赋值时赋值的位置在变量原本定义的位置 所以变量的提升,提升的其实是变量的声明,而不是变量的赋值
function foo() {
var a = 1
console.log(a) // 1
console.log(b) // undefined
var b = 2
}
foo()
function foo() {
var a = 1
console.log(a) // 1
console.log(b) // undefined
var b = 2
}
foo()
解析为:
function foo() {
var a
var b
a = 1
console.log(a) // 1
console.log(b) // undefined
b = 2
}
foo()
function foo() {
var a
var b
a = 1
console.log(a) // 1
console.log(b) // undefined
b = 2
}
foo()
函数提升则是整个函数整体提升
function foo() {
console.log(a) //a()
var a = 1
console.log(a) //1
function a() {}
console.log(a) //1
}
foo()
function foo() {
console.log(a) //a()
var a = 1
console.log(a) //1
function a() {}
console.log(a) //1
}
foo()
解析为:
function foo() {
var a
function a() {}
console.log(a) // a()
a = 1
console.log(a) // 1
console.log(a) // 1
}
foo()
function foo() {
var a
function a() {}
console.log(a) // a()
a = 1
console.log(a) // 1
console.log(a) // 1
}
foo()
复杂一点的例子
function foo() {
var a
var b
function b() {}
function a() {}
console.log(a) // a()
a = 1
console.log(a) // 1
console.log(b) // b()
b = 2
console.log(b) //2
}
function foo() {
var a
var b
function b() {}
function a() {}
console.log(a) // a()
a = 1
console.log(a) // 1
console.log(b) // b()
b = 2
console.log(b) //2
}
解析为:
function foo() {
var a
var b
function b() {}
function a() {}
console.log(a) // a()
a = 1
console.log(a) // 1
console.log(b) // b()
b = 2
console.log(b) //2
}
function foo() {
var a
var b
function b() {}
function a() {}
console.log(a) // a()
a = 1
console.log(a) // 1
console.log(b) // b()
b = 2
console.log(b) //2
}
隐式全局变量不会提升
function foo() {
console.log(a)
console.log(b) // 报错 b is not defined
b = 'aaa'
var a = 'bbb'
console.log(a)
console.log(b)
}
foo()
function foo() {
console.log(a)
console.log(b) // 报错 b is not defined
b = 'aaa'
var a = 'bbb'
console.log(a)
console.log(b)
}
foo()
面试题
console.log(a) //a()
a() //10
var a = 3
function a() {
console.log(10)
}
console.log(a) //3
a = 6
a() // a is not a function
console.log(a) //a()
a() //10
var a = 3
function a() {
console.log(10)
}
console.log(a) //3
a = 6
a() // a is not a function
自己调用自己,需要一个结束条件来结束自我调用
原理:每次 return 都会结束函数的运行,但 return 时又调用了函数,从而继续运行下一次函数,又继续 return,又继续调用,直到满足结束条件,没有下一次调用
function sum(n) {
if (n == 1) return 1
return sum(n - 1) + n
}
const amount = sum(5)
console.log(amount) //15
function sum(n) {
if (n == 1) return 1
return sum(n - 1) + n
}
const amount = sum(5)
console.log(amount) //15
题目 1:传递两个参数 m,n,返回长度为 m,所有元素都为 n 的数组
function fn(m, n) {
return m ? fn(m - 1, n).concat(n) : []
}
console.log(fn(5, 4))
;[4, 4, 4, 4, 4]
function fn(m, n) {
return m ? fn(m - 1, n).concat(n) : []
}
console.log(fn(5, 4))
;[4, 4, 4, 4, 4]
js-自调用
const add = (function add() {})()
const add = (function add() {})()
js-||
0 算 false
const a = false || 1
console.log(a) // 1
const b = 0 || 1
console.log(b) // 1
const a = false || 1
console.log(a) // 1
const b = 0 || 1
console.log(b) // 1
const a = 0
if (a) {
console.log(1)
}
const a = 0
if (a) {
console.log(1)
}
undefined | '' // 结果为0,interesting
undefined | '' // 结果为0,interesting
js-return
return 赋值语句的结果是 return 最左侧的变量
let a = 1
const b = 2
function name() {
return (a = b)
}
console.log(name())
let a = 1
const b = 2
function name() {
return (a = b)
}
console.log(name())
return 有 true 则会 return true
const a = false
const b = true
function name(params) {
return a || b
}
console.log(name()) //true
const a = false
const b = true
function name(params) {
return a || b
}
console.log(name()) //true
const a = false
const b = false
function name() {
return a || b
}
console.log(name()) //false
const a = false
const b = false
function name() {
return a || b
}
console.log(name()) //false
==和===区别
简单来说: == 代表相同, ===代表严格相同
当进行双等号比较时候: 先检查两个操作数数据类型,如果相同, 则进行===比,如果不同, 则愿意为你进行一次类型转换, 转换成相同类型后再进行比较, 而===比较时, 如果类型不同,直接就是 false
操作数 1 == 操作数 2, 操作数 1 === 操作数 2
比较过程:
双等号==:
(1)如果两个值类型相同,再进行三个等号(===)的比较
(2)如果两个值类型不同,也有可能相等,需根据以下规则进行类型转换在比较:
1)如果一个是 null,一个是 undefined,那么相等
2)如果一个是 0,一个是 false,那么相等
3)如果一个是字符串,一个是数值,把字符串转换成数值之后再进行比较
三等号===:
(1)如果类型不同,就一定不相等
(2)如果两个都是数值,并且是同一个值,那么相等;如果其中至少一个是 NaN,那么不相等。(判断一个值是否是 NaN,只能使用 isNaN( ) 来判断)
(3)如果两个都是字符串,每个位置的字符都一样,那么相等,否则不相等。
(4)如果两个值都是 true,或是 false,那么相等
(5)如果两个值都引用同一个对象或是函数,那么相等,否则不相等
(6)如果两个值都是 null,或是 undefined,那么相等
const a = 0
console.log(a==false) // true
const a = 0
console.log(a==false) // true
js-window.onload
window 页面加载完成时调用
window.onload = function () {}
window.onload = function () {}
js-localStorage/sessionStorage
使用 localStorage 创建一个本地存储的 name/value 键值对
localStorage 和 sessionStorage 属性允许在浏览器中存储 key/value 对的数据。
localStorage 用于长久保存整个网站的数据,保存的数据没有过期时间,直到手动去删除
sessionStorage 只将数据保存在当前会话中,该数据对象临时保存同一窗口(或标签页)的数据,在关闭窗口或标签页之后将会删除这些数据
localStorage 本质上是对字符串的读取,有 json 格式时需要 JSON.stringify()转化为字符串。
localStorage.setItem('text', 'hello') //第一种存储方式
localStorage.text = 'world' //第二种存储方式
localStorage.getItem('text') //调用getItem方法取值
localStorage.text //直接取值
localStorage.removeItem('text') //删除localStorage
localStorage.setItem('text', 'hello') //第一种存储方式
localStorage.text = 'world' //第二种存储方式
localStorage.getItem('text') //调用getItem方法取值
localStorage.text //直接取值
localStorage.removeItem('text') //删除localStorage
addEventListener 和 on 的区别
addEventListenert 第一个参数为事件名,第二个参数为函数,第三个参数为 Boolean,为 true 执行捕获机制,如果是 false 执行冒泡机制,可以省略,默认 false
div.addEventListener('click', () => {
console.log('click')
})
div.addEventListener('click', () => {
console.log('click')
})
- on 同一个事件只能绑定单个方法,因为绑定多个,后一个会覆盖前一个
- addEventListener 可以给同一个事件绑定多个方法,且方法间不会覆盖,自上而下依次执行
- addEventListener 可以利用第三个参数决定采用事件冒泡还是事件捕获
- addEventListener 它对任何 DOM 元素都是有效的,而不仅仅只对 HTML 元素有效(DOM 不是专为 HTML 设计的,它是通用型的标准,为所有标记语言(如 java 中常见的 xml 等)而设计)
- addEventListener 为 DOM2 级事件绑定,onclick 为 DOM0 级事件绑定()
- 在移除事件上,on 是指针指向 null,例如 document.onclick = null,而 addEventListener 则使用的是独有的移除方法 removeListener(要使用此方法,addEventListener 必须执行的是外部函数或存在函数名,不然则不能使用)
- IE678 只能使用 attachEvent,无 addEventListener
总结:推荐使用addEventListener
鼠标事件
toucshtart // 鼠标点击
touchmove // 鼠标离开
toucshtart // 鼠标点击
touchmove // 鼠标离开
js-dom 事件级别
- 0 级只能定义一个事件,多事件会互相覆盖
- 1 级没有事件
- 2 级多出了事件的自定义,并且还有事件冒泡和捕获。注意 ie11 以下的写法(IE 事件处理程序没有第三个参数,因为 IE 早期版本只支持事件冒泡,所以默认就是事件冒泡)
- 3 级多了自定义事件,和一起其他扩展的事件
js-dom 操作
获取 dom
document.querySelector // css选择器,获取单个dom
document.querySelectorAll // css选择器,获取全部dom
document.querySelector // css选择器,获取单个dom
document.querySelectorAll // css选择器,获取全部dom
是否包含类名
element.classList.contains('类名')
element.classList.contains('类名')
添加和移除类名
element.classList.add('类名')
element.classList.remove('类名')
element.classList.add('类名')
element.classList.remove('类名')
创建 dom
document.createElement('div')
document.createElement('div')
js-for 循环
continue 和 break 的区别:
continue
终止本次循环,接着还执行下一次循环
break
用于完全结束一个循环,跳出循环体执行循环后面的语句
因为 js 中 for 是没有局部作用域的概念的,所以只有把 for 循环放在函数中时,才可以在 for 循环中使用 return 语句
console.log
设置 console.log 颜色
调试 js 代码的时候,为调试的日志添加样式可以使信息更醒目
第一个参数就是要输出的字符串,通过%c
分割的区间与之后的参数一一对应,参数就是标准的 css,如果对应的参数不足,无法匹配%c
会以字符串的形式输出,参数过多就会直接以字符串形式输出多余的样式。
console.log(
'%cBinfoTeamFeUI',
'font-size:20px;color:blue;background:yellow;',
BinfoTeamFeUI
)
console.log(
'%cBinfoTeamFeUI',
'font-size:20px;color:blue;background:yellow;',
BinfoTeamFeUI
)
setTimeout
setTimeout(fn,millisec) 方法用于在指定的毫秒数后调用函数或计算表达式 很简单,setTimeout() 只执行 fn 一次,到底什么时候执行取决于第二个参数 millisec 设定的毫秒数,所以很多人习惯上称之为延迟,无非就是延迟一段时间后再执行里面的代码
setTimeout(function () {
console.log('我是setTimeout')
}, 1000)
setTimeout(function () {
console.log('我是setTimeout')
}, 1000)
正常情况下,等 1000 毫秒以后会在浏览器的控制台输出
OK,看一个例子,这个例子的输出结果是什么?
setTimeout(function () {
console.log(1)
}, 0)
console.log(2)
console.log(3)
setTimeout(function () {
console.log(1)
}, 0)
console.log(2)
console.log(3)
出乎一些人的意料,得到的结果竟然是2、3、1
。这似乎不按套路出牌啊,明明是等待了 0 毫秒也就是不等待直接输出啊,为啥 1 却在最后输出了呢?
这就需要搞清楚一个很重要的概念:js是单线程的
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着
其实很好理解,就像大家去超市买东西一样,所有买东西的人都需要在收银台排队结账,正常情况下每个收银台同一时间只能为一位顾客结账,这位顾客结账完成才能为下一位顾客服务
而浏览器的内核是多线程的,它们在内核制控下相互配合以保持同步,一个浏览器至少实现三个常驻线程:javascript 引擎线程,GUI 渲染线程,浏览器事件触发线程
javascript 引擎是基于事件驱动单线程执行的,JS 引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个 JS 线程在运行 JS 程序
GUI 渲染线程负责渲染浏览器界面,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。但需要注意 GUI 渲染线程与 JS 引擎是互斥的,当 JS 引擎执行时 GUI 线程会被挂起,GUI 更新会被保存在一个队列中等到 JS 引擎空闲时立即被执行
事件触发线程,当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待 JS 引擎的处理。这些事件可来自 JavaScript 引擎当前执行的代码块如 setTimeOut、也可来自浏览器内核的其他线程如鼠标点击、AJAX 异步请求等,但由于 JS 的单线程关系所有这些事件都得排队等待 JS 引擎处理。(当线程中没有执行任何同步代码的前提下才会执行异步代码)
其实,当 js 代码执行遇到 setTimeout(fn,millisec)时,会把 fn 这个函数放在任务队列中,当 js 引擎线程空闲时并达到 millisec 指定的时间时,才会把 fn 放到 js 引擎线程中执行
HTML5 标准规定了 setTimeout()的第二个参数的最小值(最短间隔),不得低于 4 毫秒,如果低于这个值,就会自动增加。在此之前,老版本的浏览器都将最短间隔设为 10 毫秒。另外,对于那些 DOM 的变动(尤其是涉及页面重新渲染的部分),通常不会立即执行,而是每 16 毫秒执行一次。这时使用 requestAnimationFrame()的效果要好于 setTimeout() 。
需要注意的是,setTimeout()只是将事件插入了"任务队列",必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定会在 setTimeout()指定的时间执行
setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,也就是说,尽可能早得执行。它在"任务队列"的尾部添加一个事件,因此要等到同步任务和"任务队列"现有的事件都处理完,才会得到执行
同步/异步
单线程/主线程
JavaScript 是单线程的。所谓单线程,就是指在 JS 引擎中负责解释和执行 JavaScript 代码的线程只有一个,一次只能完成一件任务。如果有多个任务,就必须排队,等待前面一个任务完成,再执行后面一个任务。如果一个任务耗时过长,那么后面的任务就必须一直等待下去,会拖延整个程序。我们不妨叫它主线程
工作线程
实际上还存在其他的线程。例如:处理 AJAX 请求的线程、处理 DOM 事件的线程、定时器线程、读写文件的线程(例如在 Node.js 中)等等。这些线程可能存在于 JS 引擎之内,也可能存在于 JS 引擎之外,在此我们不做区分。统称为工作线程
同步异步
Javascript 语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)
同步就是后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的
异步用于解决主线程处理时间较长或时间不确定的任务造成阻塞的情况,把这样的任务交给工作线程去处理,在此期间主进程不用等待工作进程,而是可以一个接一个地处理自己任务,如果又遇到时间不确定的任务,就接着交给工作线程去处理
异步的构成要素
异步函数实际上很快就调用完成了。但是后面还有工作线程执行异步任务、通知主线程、主线程调用回调函数等很多步骤。我们把整个过程叫做异步过程。异步函数的调用在整个异步过程中,只是一小部分
总结一下,一个异步过程通常是这样的:
主线程发起一个异步请求,相应的工作线程接收请求并告知主线程已收到(异步函数返回);主线程可以继续执行后面的代码,同时工作线程执行异步任务;工作线程完成工作后,通知主线程;主线程收到通知后,执行一定的动作(调用回调函数)。
它可以叫做异步过程的发起函数,或者叫做异步任务注册函数。args 是这个函数需要的参数。callbackFn 也是这个函数的参数,但是它比较特殊所以单独列出来。所以,从主线程的角度看,一个异步过程包括下面两个要素:发起函数(或叫注册函数)A 和回调函数 callbackFn。它们都是在主线程上调用的,其中注册函数用来发起异步过程,回调函数用来处理结果。
消息队列和事件循环
异步过程中,工作线程在异步操作完成后需要通知主线程。这个通知机制是利用消息队列和事件循环实现的
工作线程将消息放到消息队列,主线程通过事件循环过程去取消息
- 消息队列:消息队列是一个先进先出的队列,它里面存放着各种消息
- 事件循环:事件循环是指主线程重复从消息队列中取消息、执行的过程
实际上,主线程只会做一件事情,就是从消息队列里面取消息、执行消息,再取消息、再执行。当消息队列为空时,就会等待直到消息队列变成非空。而且主线程只有在将当前的消息执行完成后,才会去取下一个消息。这种机制就叫做事件循环机制,取一个消息并执行的过程叫做一次循环
事件循环用代码表示大概是这样的:
那么,消息队列中放的消息具体是什么东西?消息的具体结构当然跟具体的实现有关,但是为了简单起见,我们可以认为:消息就是注册异步任务时添加的回调函数。
主线程在执行完当前循环中的所有代码后,就会到消息队列取出这条消息(也就是 message 函数),并执行它。到此为止,就完成了工作线程对主线程的通知,回调函数也就得到了执行。如果一开始主线程就没有提供回调函数,AJAX 线程在收到 HTTP 响应后,也就没必要通知主线程,从而也没必要往消息队列放消息。
异步与事件
上文中说的“事件循环”,为什么里面有个事件呢?那是因为:消息队列中的每条消息实际上都对应着一个事件
上文中一直没有提到一类很重要的异步过程:DOM 事件。举例来说
从事件的角度来看,在按钮上添加一个鼠标单击事件的事件监听器;当用户点击按钮时,鼠标单击事件触发,事件监听器函数被调用
从异步过程的角度看,addEventListener 函数就是异步过程的发起函数,事件监听器函数就是异步过程的回调函数。事件触发时,表示异步任务完成,会将事件监听器函数封装成一条消息放到消息队列中,等待主线程执行。
事件的概念实际上并不是必须的,事件机制实际上就是异步过程的通知机制。我觉得它的存在是为了编程接口对开发者更友好。
另一方面,所有的异步过程也都可以用事件来描述。例如:setTimeout 可以看成对应一个时间到了的事件。前文的 setTimeout(fn, 1000);可以看成:
同步异步例子
用一个生活中的例子总结一下同步和异步:在公路上,汽车一辆接一辆,有条不紊的运行。这时,有一辆车坏掉了。假如它停在原地进行修理,那么后面的车就会被堵住没法行驶,交通就乱套了。幸好旁边有应急车道,可以把故障车辆推到应急车道修理,而正常的车流不会受到任何影响。等车修好了,再从应急车道回到正常车道即可。唯一的影响就是,应急车道用多了,原来的车辆之间的顺序会有点乱
这就是同步和异步的区别。同步可以保证顺序一致,但是容易导致阻塞;异步可以解决阻塞问题,但是会改变顺序性
生产者与消费者
从生产者与消费者的角度看,异步过程是这样的:工作线程是生产者,主线程是消费者(只有一个消费者)。工作线程执行异步任务,执行完成后把对应的回调函数封装成一条消息放到消息队列中;主线程不断地从消息队列中取消息并执行,当消息队列空时主线程阻塞,直到消息队列再次非空。
同步异步写法
回调函数
function f1(callback) {
setTimeout(function () {
callback()
}, 1000)
}
function f2() {
console.log('f2运行了')
}
f1(f2)
function f1(callback) {
setTimeout(function () {
callback()
}, 1000)
}
function f2() {
console.log('f2运行了')
}
f1(f2)
事件监听
发布/订阅
Promises
打开新网页
wIndow.open 在新窗口打开网页
window.open('meeting/doc/doc2/lookFile.html?url')
window.open('meeting/doc/doc2/lookFile.html?url')
通过 a 标签在新窗口打开网页
const a = document.querySelector("a")
a!.setAttribute("href", (window as any).BASE_URL + "meeting/doc/doc2/lookFile.html?url=" + path)
a!.click()
const a = document.querySelector("a")
a!.setAttribute("href", (window as any).BASE_URL + "meeting/doc/doc2/lookFile.html?url=" + path)
a!.click()
window.location.href 会替换当前网页
window.location.href = 'meeting/doc/doc2/lookFile.html?url'
window.location.href = 'meeting/doc/doc2/lookFile.html?url'