Hellom's Studio.

复盘技能之 JavaScript

字数统计: 3.2k阅读时长: 14 min
2020/01/13 Share

参考链接:

面试题的思考:

拿到面试题 首先看 考点

题会变 考点不变 不变应万变

高效学习三部曲:

  • 找准知识体系

  • 刻意训练

  • 及时反馈

建立知识体系

JS 基础语法

变量类型和计算

值类型

1
2
3
4
5
6
null
undefined
String
Number
Boolean
Symbol // es6 新增基础数据类型 标识唯一的ID

引用类型

1
2
Array
Object

typeof

深拷贝

变量计算

if 语句中判断的就是以下两种:

  • truly 变量: !!a === true

  • falsely 变量:!!a === false

1
2
3
4
5
6
7
// 以下为 falsely变量,其他都是truly变量
!!0 === false
!!'' === false
!!NaN === false
!!null === false
!!undefined === false
!!false === false

关于 == 转换规则:

  • 1.如果一项操作数是布尔值,在比较之前转换为数值,false => 0 , true => 1

  • 2.如果一项操作数是字符串,另外一个是数值,在比较之前将字符串转换为数值

  • 3.如果一项操作数是对象,另外一个不是,调用对象的 valueOf() 方法,用得到的基本类型按照前面的规则比较,如果没有则调用 toString() 方法

  • 4.null 和 undefined 是相等

  • 5.在比较相等之前,不能将 null 和 undefined 转换为其他任何值

  • 6.如果一项操作数是 NaN,相等返 fasle ,不相等返 true。即使两个操作数都是 NaN 类型的,相等还是返 fasle ,因为 NaN 不等于 NaN

  • 7.如果两个操作数都是对象,则比较它们是不是同一个对象,如果两个操作数都指向同一个对象,那操作符返 true,否则为 false

ps:{} 和 [] 是 引用类型,引用类型是存放在堆内存里的,栈内存有一个或者多个地址 指向这个堆内存对应的数据,所以 引用类型在进行 == 操作符时,比较的是地址,而不是真实的值。

关于 ![] == [] 返回true!{} == {} 返回false 的问题

![] == [] 返回true的推导过程:

根据运算符规则 ,! 是大于 == ,所以先执行 ![]

首先 ! 可将变量转换成 boolean 类型,null、 undefind、’’、 NaN 取反为 true,其他都为 false。

也就是 ![] == [] 相当于 false == []

根据规则一 ,false => 0

那么 false == [] 相当于 0 == []

在根据规则三 , [].toSting() => '' 为空字符串

则转换成 0 == ''

在根据规则二 Number('') => 0

0 == 0 结果自然为 true 啦

!{} == {} 返回false 则是以下的推导过程:

!{} == {} ==> false == {} ==> 0 == {} ==> 0 == {}.toString() ==> 0 == [object object] ==> false

===: 严格模式下使用 仅比较不转换

1
2
'5' == 5 // true
'5' === 5 // false

原型和原型链

class 和继承

类型判断 instanceof

原型和原型链

作用域和闭包

知识点:

作用域分以下三种:

  • 全局作用域

  • 函数作用域

  • 块级作用域

自由变量

  • 一个变量在当前作用域没有定义 但被使用了

  • 就会向上级作用域 一层一层依次查找 直到找到为止

  • 如果到全局作用域中还没找到 就会报 xx is not defined

闭包

闭包:能够访问另外一个函数作用域的变量的函数

作用域的特殊情况:

  • 函数作为参数传递
1
2
3
4
5
6
7
8
9
10
11
12
function print(fn) {
let a = 200
fn() // 函数执行时
}

const a = 100
function fn() {
// 函数定义时
console.log(a)
}

print(fn) // 100
  • 函数作为返回值被返回
1
2
3
4
5
6
7
8
9
10
function create() {
const a = 100
return function () {
console.log(a) // 函数定义时
}
}

const fn = create()
const a = 200
fn() // 100 函数执行时

重点:
所有自由变量的查找,都在函数定义的地方,向上级作用域查找,不是在函数执行的地方

this

this 的使用场景分:

  • 作为普通函数
1
2
3
4
5
function fn1() {
console.log(this)
}

fn1() // window
  • 使用 call apply bind
1
2
3
4
5
6
fn.call({ x: 100 }, a1, a2, a3) // {x: 100}

fn.apply({ x: 100 }, [a1, a2, a3])

const fn2 = fn1.bind({ x: 100 })
fn2() // {x: 100}
  • 作为对象方法被调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const zhangsan = {
name: '张三',
sayHi() {
// this 即当前对象
console.log(this)
},

wait() {
setTimeout(function () {
// this === window
console.log(this)
})
},
}
  • 在 class 方法中被调用
1
2
3
4
5
6
7
8
9
10
11
12
13
class People {
constructor(name, age) {
this.name = name
this.age = age
}

sayHi() {
console.log(this)
}
}

const zhangsan = new People('张三')
zhangsan.sayHi() // zhangsan 对象
  • 箭头函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const zhangsan = {
name: '张三',
sayHi() {
// this 即当前对象
console.log(this)
},

wait() {
setTimeout(() => {
// this 即当前对象
console.log(this)
})
},
}

重点:
this 取什么值,是函数执行的时候确定的,而不是定义的时候

异步

知识点:

单线程和异步

  • JS 是单线程语言 只能同时做一件事

  • JS 和 DOM 渲染共用同一个线程 因为 JS 可修改 DOM 结构

  • 异步是基于 JS 单线程语言的特性

  • 异步是基于 callback 函数调用

  • 异步不会阻塞代码

  • 同步会阻塞代码

  • Promise 解决 callback hell

应用场景

  • 网路请求,ajax 调用 加载图片

  • 定时任务 setTimeout

微观任务与 宏观任务

微观任务先于宏观任务

微观任务有(js 引擎发起的任务):

  • promise

  • Object.observe

宏观任务有(宿主 浏览器/node 发起的任务):

  • setTimeout

  • setInterval

JS-Web-API-DOM

DOM (Document Object Model)

DOM 的本质:就是从 HTML 文件中解析出来的树

DOM 的节点操作

1
2
3
4
5
6
7
<div id="div1" class="container">
<p id="p1">这是文本1</p>
<p>这是文本2</p>
<p>这是文本3</p>
</div>

<div id="div2"></div>
1
2
3
4
5
6
const div1 = document.getElementById('div1') // 元素
const divList = document.getElementsByTagName('div') //集合

const containerList = document.getElementsByClassName('.container') // 集合

const pList = document.querySelectorAll('p') // 集合

DOM 的结构操作

1
2
3
4
5
6
7
<div id="div1" class="container">
<p id="p1">这是文本1</p>
<p>这是文本2</p>
<p>这是文本3</p>
</div>

<div id="div2"></div>
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
const div1 = document.getElementById('div1')
const div2 = document.getElementById('div2')

const newP = document.createElement('p')
newP.innerHTML = 'this is newP'

// 新增节点
div1.appendChild(newP)

// 移动节点
const p1 = document.getElementById('p1')
div2.appendChild(p1)

// 获取父元素
console.log(p1.parentNode) // <div id="div2"></div>

// 获取子元素列表
const div1ChildNodes = div1.childNodes // 获取div1下的所有子节点
console.log(div1ChildNodes) // NodeList(7) [text, text, p, text, p, text, p]

/*
1. 转化为数组
2. 通过判断 nodeType 是否等于 1
标签的节点类型都是 1
从而过滤出 所有的 p 标签
*/

const div1ChildNodesP = Array.prototype.slice
.call(div1ChildNodes)
.filter((node) => {
if (node.nodeType === 1) {
return true
}
return false
})

console.log(div1ChildNodesP) // [p ,p ,p]

DOM 的性能

DOM 操作比较 “昂贵” ,尽量避免频繁操作 DOM

提高 DOM 的性能有以下两种方式:

  • 对 DOM 查询做缓存
1
2
3
4
5
6
7
8
9
10
11
12
// 不缓存 DOM 查询结果
for (let i = 0; i < document.querySelectorAll('p').length; i++) {
// 每次循环,都会计算length长度,频繁进行 DOM 查询
}

// 缓存 DOM 查询结果
const pList = document.querySelectorAll('p')
const length = pList.length

for (let i = 0; i < length; i++) {
// 缓存length,只进行一次 DOM 查询
}
  • 将频繁查询改为一次查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const list = document.getElementById('list')

// 创建一个文档片段,此时还没插入到 DOM 树中
const frag = document.createDocumentFragment()

// 执行插入
for (let i = 0; i < 10; i++) {
const li = document.createElement('li')
li.innerHTML = `this is item ${i}`
frag.appendChild(li)
}

//都完成之后,再一次性插入 DOM 树中
list.appendChild(frag)

JS-Web-API-BOM

BOM (Browser Object Model)

知识点:

  • navigator
1
const ua = navigator.userAgent // 查看浏览器信息
  • screen
1
2
console.log(screen.width)
console.log(screen.height)
  • location
1
2
3
4
5
6
7
8
9
10
11
console.log(location.href) //https://coding.imooc.com/lesson/115.html?a=100&b=200#mid=30378

console.log(location.protocol) // https:

console.log(location.host) // coding.imooc.com

console.log(location.search) // ?a=100&b=200

console.log(location.hash) // #mid=30378

console.log(location.pathname) // /lesson/115.html
  • history
1
2
history.back() // 后退
history.forward() // 前进

JS-Web-API-事件

事件绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 通用的事件绑定
function bindEvent(elem, type, selector, fn) {
// 判断是否传入了三个参数
if (fn == null) {
fn = selector
selector = null
}

elem.addEventListener(type, (e) => {
const target = e.target
if (selector) {
// 代理绑定
if (target.matches(selector)) {
fn.call(target, event)
}
} else {
// 普通绑定
fn.call(target, event)
}
})
}

事件冒泡

  • 事件冒泡是基于 DOM 属性结构

  • 事件会顺着触发元素向上冒泡

  • p => div => body => document

  • 应用场景:事件代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<body>
<button id="btn">按钮</button>

<div id="div1">
<p id="p1">激活</p>
<p id="p2">取消</p>
<p id="p3">取消</p>
<p id="p3">取消</p>
</div>

<div id="div2">
<p id="p5">取消</p>
<p id="p6">取消</p>
</div>
</body>
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
// 通用的事件绑定
function bindEvent(elem, type, selector, fn) {
// 判断是否传入了三个参数
if (fn == null) {
fn = selector
selector = null
}

elem.addEventListener(type, (e) => {
const target = e.target
if (selector) {
// 代理绑定
if (target.matches(selector)) {
fn.call(target, event)
}
} else {
// 普通绑定
fn.call(target, event)
}
})
}

//无论点击哪个元素都会触发这个事件
const body = document.body
bindEvent(body, 'click', (e) => {
console.log(e.target.innerHTML) //获取触发的元素
console.log('body clicked!')
})

事件代理

  • 代码简洁

  • 减少浏览器内存占用

  • 不要滥用

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
function bindEvent(elem, type, selector, fn) {
// 判断是否传入了三个参数
if (fn == null) {
fn = selector
selector = null
}

elem.addEventListener(type, (e) => {
const target = e.target
if (selector) {
// 代理绑定
if (target.matches(selector)) {
fn.call(target, event)
}
} else {
// 普通绑定
fn.call(target, event)
}
})
}

const div3 = document.getElementById('div3')
bindEvent(div3, 'click', 'a', (e) => {
e.preventDefault()
alert(e.target.innerHTML)
})

JS-Web-API-Ajax

XMLHttpRequest 状态码

xhr.readyState

  • 0:(未初始化)还没调用 send() 方法

  • 1:(载入)已经调用 send() 方法,正在发送请求

  • 2:(载入完成)send()方法执行完成,已经接收到全部响应内容

  • 3:(交互)正在解析响应内容

  • 4:(完成)响应内容解析完成,可以在客户端调用

xhr.status

  • 2xx:表示成功处理请求,例如 200

  • 3xx:需要重定向,浏览器直接跳转,例如 301,302,304

  • 4xx:客户端请求错误,例如 404,403

  • 5xx:服务器端错误,例如 500


同源策略和跨域

同源策略:

  • ajax 请求时 浏览器要求网页和 server 必须同源

  • 同源:协议 域名 端口 必须一致

  • 加载图片 css js 可无视同源策略

1
2
3
<img src="跨域的图片地址" />
<link href="跨域的css地址" />
<script src="跨域的js地址"></script>

跨域:

  • 所有的跨域,都必须经过 server 端允许和配合

  • 未经 server 端允许就实现跨域,说明浏览器有漏洞

JSONP:

  • <script> 可绕过跨域限制

  • 服务器可以拼接任意数据返回

  • <script> 可以获取跨域数据 只要服务端配合返回

JSONP 原理: 允许客户端传一个 callback 参数给服务端,服务端返回数据时会将 callback 参数作为函数名来包裹 JSON 数据,这样客户端就定义函数来接收返回的数据

CORS

  • 服务器设置 http header

ajax 常用插件:

  • jquery

  • fetch

  • axios


存储

cookie

  • 本身是用于与服务端通信的
  • 被借用来做本地存储的

cookie 的缺点

  • 存储限时 4kb
  • 只能通过 document.cookie = ’…‘的方式增加
  • 每调一次接口 都会带上 cookie 请求

HTML5 存储
localStorage: 永久存储 除非手动或者代码删除
sessionStorage: 仅存在当前会话中 浏览器关闭就会清空

开发环境

页面加载过程
性能优化

性能优化原则:

  • 多使用内存 缓存或其他方法

  • 减少 cpu 计算量 减少网络加载耗时
    简单来说 就是空间换时间

让渲染更快:

  • css 文件放在 head 里 js 文件放在 body 之后

  • 懒加载(上滑加载更多 图片懒加载)

  • 尽早执行 js 使用 DOMContentLoaded 触发

题目:

3. window.onload 和 DOMContentLoaded 的区别

考点:页面加载过程

  • window.onload:等到页面全部加载完成后执行,包括图片、视频等媒体资源

  • DOMContentLoaded:等到 dom 节点渲染后就执行,不包括图片、视频等媒体资源

4. JS 创建 10 个 a 标签,点击的时候弹出对应的序号

考点:JS 作用域

1
2
3
4
5
6
7
8
9
10
let a
for (let i = 0; i < 10; i++) {
a = document.createElement('li')
a.innerHTML = i + '</br>'
a.addEventListener('click', function (e) {
e.preventDefault()
alert(i)
})
document.body.appendChild(a)
}

5. 手写节流 throttle 、防抖 debounce

考点:性能、体验优化

6. promise 解决了什么问题?

考点:JS 异步

20.手写一个简易的 ajax

21. 从输入 url 到渲染的页面整个过程

页面过程:

  • DNS 解析:域名 => ip 地址
  • 通过 ip 地址去向服务器发起 http 请求
  • 服务器处理 http 请求 并返回数据给浏览器

渲染过程:

  • 根据 html 代码 生成 DOM tree
  • 根据 css 代码 生成 CSSOM
  • 生成的 DOM 和 CSSOM 整合成 Render tree
  • 根据 Render tree 开始渲染页面
  • 渲染遇到 <script>停止渲染 优先加载 JS 代码 完成后再继续
  • 直到页面全部渲染完成
CATALOG
  1. 1. 参考链接:
  2. 2. 面试题的思考:
  3. 3. 高效学习三部曲:
  4. 4. 建立知识体系
  • JS 基础语法
    1. 1. 变量类型和计算
      1. 1.1. 值类型
      2. 1.2. 引用类型
      3. 1.3. typeof
      4. 1.4. 深拷贝
      5. 1.5. 变量计算
    2. 2. 原型和原型链
    3. 3. 作用域和闭包
      1. 3.1. 作用域分以下三种:
      2. 3.2. 自由变量
      3. 3.3. 闭包
      4. 3.4. this
    4. 4. 异步
      1. 4.1. 单线程和异步
      2. 4.2. 应用场景
      3. 4.3. 微观任务与 宏观任务
    5. 5. JS-Web-API-DOM
      1. 5.1. DOM 的节点操作
      2. 5.2. DOM 的结构操作
      3. 5.3. DOM 的性能
    6. 6. JS-Web-API-BOM
    7. 7. JS-Web-API-事件
      1. 7.1. 事件绑定
      2. 7.2. 事件冒泡
      3. 7.3. 事件代理
    8. 8. JS-Web-API-Ajax
      1. 8.1. XMLHttpRequest 状态码
      2. 8.2. 同源策略和跨域
      3. 8.3. ajax 常用插件:
    9. 9. 存储
    10. 10. 开发环境
      1. 10.0.0.1. 页面加载过程
      2. 10.0.0.2. 性能优化
  • 11. 题目:
    1. 11.1. 3. window.onload 和 DOMContentLoaded 的区别
      1. 11.1.1. 考点:页面加载过程
    2. 11.2. 4. JS 创建 10 个 a 标签,点击的时候弹出对应的序号
      1. 11.2.1. 考点:JS 作用域
    3. 11.3. 5. 手写节流 throttle 、防抖 debounce
      1. 11.3.1. 考点:性能、体验优化
    4. 11.4. 6. promise 解决了什么问题?
      1. 11.4.1. 考点:JS 异步
    5. 11.5. 20.手写一个简易的 ajax
    6. 11.6. 21. 从输入 url 到渲染的页面整个过程