JS_review
Jonnzer Lv4

变量类型和计算

值类型和引用类型,分别是什么,有什么区别

JS的基本数据类型就是值类型,包括 Undefined, Null, Boolean, Number, String, Symbol
JS的引用类型,统称为Object类型,包括 Object,Array,Date,RegExp,Function

1
2
3
4
5
6
7
8
9
const obj1 = { X: 100 }
const obj2 = obj1
let x1 = obj1.X
obj2.X = 101
x1 = 102

console.log(obj1) // { X:101 }
console.log(obj2) // { X:101 }
console.log(x1) // 102
typeof 能判断哪些类型

typefof 能判断所有基本类型数据,
对于引用类型 :function => function
null 、 数组、对象 => Object (null 属于例外情况)

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

// 是否为 null:
var a = null;
(!a && typeof a === 'object') // true

var a;
typeof a; // undefined

a = null;
typeof a; // object

a = true;
typeof a; // boolean

a = 666;
typeof a; // number

a = "hello";
typeof a; // string

a = Symbol();
typeof a; // symbol

a = function(){}
typeof a; // function

a = [];
typeof a; // object
a = {};
typeof a; // object
a = /aaa/g;
typeof a; // object

判断全局变量有无时需要用到:

1
2
3
4
5
6
7
if (DEUBG) { // maybe throw error about not DEBUG 
console.log('debugging is start')
}

if (typeof DEBUG !== 'undefined') { // better
console.log('debugging is start')
}
何时使用 === ,何时使用 ==

“ == 允许在相等比较中进行强制类型转换,而 === 不允许。”

JS深浅拷贝

在另一篇博文有详细介绍,链接:点此

类型转换:

在另一篇博文有详细介绍,链接:点此


原型和原型链

如何准备判断一个变量是数组?

a instanceof Array

class 的原型本质?

原型和原型链的原理

jQuery 的基本结构?用 class 实现?要考虑插件和扩展性。
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
 class jQuery {
constructor(selector) { // dom 查询
const result = document.querySelectorAll(selector)
const length = result.length
for (let i = 0; i < length; i++) { // dom 绑定到实例上
this[i] = result[i]
}
this.length = length
this.selector = selector
get(index) { // 获取 对应的 dom
return this[index]
}
each(fn) { // 添加遍历行为
for (let i = 0; i < this.length ; i++) {
const elem = this[i]
fn(elem)
}
}
on(type, fn) { // 循环 添加事件监听
return this.each(elem => {
elem.addEventListener(type, fn, false)
})
}
}

}
// execute

const $p = new jQuery('p')
$p.get(1)
$p.each((elem) => console.log(elem.nodeName))
$p.on('click', () => alert('clicked'))

// 插件 在原型链上添加
jQuery.prototype.dialog = function(info) {
alert(info)
}

// 扩展性 == 添加新方法 或者造轮子
class myjQuery extends jQuery {
constructor(selector) {
super(selector)
}
// 扩展...
addClass(className) {

},
style() {

}
}

在另一篇博文有详细介绍,链接:点此

作用域和闭包

this 的不同应用场景,如何取值?

(1)作为普通函数 ,返回 window
(2)使用 call 、apply 、bind ,返回绑定的参数
(3)作为对象方法被调用,返回对象本身
(4)在 class 方法中被调用,返回 实例
(5)箭头函数: 取的是上级作用域的调用者

this 的值 是执行时确定的,而不是函数定义时。

1
2
3
4
5
6
7
8
function fn1() {
console.log(this)
}
fn1() // window

fn1.call({ x: 100 }) // { x: 100 }
const fn2 = fn1.bind({ x: 200 }) // bind 会返回一个新函数
fn2() // {x: 200}

箭头函数中 和 setTimeout中 对 this的使用:
this_1

class 中 this的指向:实例本身

1
2
3
4
5
6
7
8
9
10
11
class People {
constructor(name) {
this.name = name
this.age = 20
}
sayHi () {
console.log(this)
}
}
const john = new People('john')
john.sayHi() // john 实例
手写 bind 函数?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function fn1(a , b, c) {
console.log('this: ', this)
console.log(a, b, c)
return 'this is fn1'
}
const fn2 = fn1.bind({x: 100}, 10, 20, 30) // function.prototype.bind
const res = fn2()
console.log(res)

Function.prototype.bind1 = function () { // custom function bind1
const args = Array.prototype.slice.call(arguments) // 将参数转为数组 slice 方法可以用来将一个类数组(Array-like)对象/集合转换成一个新数组
const t = args.shift()// 获取 this
const self = this // 存 调用者
// 返回一个函数
return function () {
self.apply(t, args)
}
}

在另一篇博文有详细介绍 call 、 apply、 bind,链接:点此

闭包是什么,实际开发中闭包的应用场景,举例说明:

闭包是作用域应用的特殊情况,有两种表现:
(1)函数作为参数被传递

1
2
3
4
5
6
7
8
9
function print(fn) {
const a = 200
fn()
}
const a = 300
function fn() {
console.log(a)
}
print(fn)

(2)函数作为返回值被返回

1
2
3
4
5
6
7
8
9
function create() {
const a = 100
return function () {
console.log(a)
}
}
const a = 200
const fn = create()
fn()

闭包在开发中的实际应用:
(1)隐藏数据,避免外界修改或污染某值
(2)做一个简单的 cache 工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function createCache() {
const data = {} // 闭包中的数据,被隐藏,不被外界访问
return {
set: function (key, val) {
data[key] = val
},
get: function (key) {
return data[key]
}
}
}
const c = createCache()
c.set('x', 100)
c.get('x') // 100
10个 a 标签,点击弹出的序号是?
1
2
3
4
5
6
7
8
9
10
11
12
let i, a
for (i = 0; i < 10; i++) { // i 在全局,结局是 alert 打印的都是 10
a = document.createElement('a')
a.innerHTML = i + '<br>'
a.addEventListener('click', function(e) {
e.preventDefault()
alert(i)
})
document.body.appendChild(a)
}
for (let i = 0; ...) // 这样可以解决

作用域 和 自由变量 分别是什么?

作用域包括:全局作用域 、函数作用域 、块级作用域( ES6 新增 )。

1
2
3
4
5
6
7
8
9
10
11
// ES6 块级作用域
if (true) {
let x = 100
}
console.log(x) // throw error: x is not defined


if (true) {
var y = 101
}
console.log(y) // undefined

自由变量:
(1)一个变量在当前作用域没有定义,但被使用了
(2)向上级作用域,一层一层的寻找,直至找到
(3)如果到全局作用域都没找到,则报错:is not defined
(4)自由变量的查找,是在函数定义的地方,向上级作用域查找。不是在执行的地方

let var const


异步 和 单线程

为什么说 JS 是单线程语言?

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

为了利用多核 CPU 的计算能力, HTML5提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程,但是子线程完全受主线程控制,且不得操作 DOM 。所以,这个新标准并没有改变 JavaScript 单线程的本质。

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

也是因为单线程机制,JS 发生请求 ,如果没有异步的话,会一直卡住接下来的 JS 运行,一直等待到结果返回,这样是不合理的,浪费了时间资源。

异步:

遇到等待(网络请求 、 定时任务)不能卡住

以 callback 回调函数形式

同步和异步的区别是什么?

同步会阻塞代码执行

异步不会阻塞代码执行, 就是为了处理 JS 单线程时的执行问题

Promise 的由来?callback hell 是什么?

callback hell ,就是传说中的回调地狱。

1
2
3
4
5
6
7
8
9
$.get(url1, (data1) => {
console.log(data1)
$.get(url2, (data2) => {
console.log(data2)
$.get(url3, (data3) => {
console.log(data3)
})
})
})
Promise 有哪三种状态?如何变化?

待定( pending ): 初始状态,既没有被兑现,也没有被拒绝。
已兑现( fulfilled ): 意味着操作成功完成。
已拒绝( rejected ): 意味着操作失败。

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
// test 1
const p1 = new Promise((resolve, reject) => {

})
console.log(p1) // Promise <pending>

// test 2
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
})
})
console.log(p2) // Promise <pending> 但点击开时,已是完成的状态 [[PromiseState]]: "fulfilled"

// test 3
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject()
})
})
console.log(p3) // Promise <pending> 但点击开时,已是完成的状态 [[PromiseState]]: "rejected"

// test 4
const p4 = Promise.resolve(100)
p4.then(data => {
console.log(data) // 100
})
promise then 和 catch 的连接

then 正常返回 resolved ,里面有 throw Error 则返回 rejected
catch 正常返回 resolved , 里面有 throw Error 则返回 rejected

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 验证 reject catch
const p3 = Promise.reject('my error').catch(err => {
console.error(err)
})
console.log('p3: ', p3)
p3.then(() => {
console.log(1) // 可以触发的
})
// Promise <pending> 但点击开时,已是完成的状态 [[PromiseState]]: "fulfilled"

const p4 = Promise.reject('my new Error').catch(err => {
throw new Error('catch err')
})
// Promise <pending> 但点击开时,已是完成的状态 [[PromiseState]]: "rejected"
console.log('p4: ', p4)

p4.then(() => {
console.log(2) // 无法触发
}).catch(err => {
console.log('error') // 可以触发
})
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
// test 1
Promise.resolve().then(() => {
console.log(1)
}).catch(() => {
console.log(2)
}).then(() => {
console.log(3)
}) // 1 3

// test 2
Promise.resolve().then(() => {
console.log(1)
throw new Error('error1')
}).catch(() => {
console.log(2)
}).then(() => {
console.log(3)
}) // 1 2 3

// test 3
Promise.resolve().then(() => {
console.log(1)
throw new Error('error1')
}).catch(() => {
console.log(2)
}).catch(() => {
console.log(3)
})
// 1 2

(async function() {
console.log('start')
const a = await 100 // await 100 === await Promise.resolve(100)
console.log('a: ', a)
const b = await Promise.resolve(200)
console.log('b: ', b)
const c = await Promise.reject(300)
console.log('c: ', c)
console.log('end')
})()
手写用 Promise 加载一张图片?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const url = 'https://cdn.jsdelivr.net/gh/jonnzer/saveImgForYun@master/test1/this_1.webp'
function loadImg(src) {
return new Promise((resolve, reject) => {
const img = document.createElement('img')
img.onload = () => { resolve(img) }
img.onerror = () => {
reject(new Error(`图片加载失败${src}`))
}
img.src = src
document.body.appendChild(img)
})
}
loadImg(url).then((res) => {
console.log(res)
return 2
}).then((res2) => {
console.log(res2)
})

async await 写法
(async function() {
const img1 = await loadImg(src1)
console.log(img1)
})()
async await

是 promise 的语法糖

async / await 用同步语法来写,取代回调函数写法

async 函数返回的是 Promise 对象

await 相当于 Promise 的 then

try … catch 可以 捕获异常

!!! async 里面 ,await 之前的代码(包括 await )都会立即执行,await 之后的代码,也是等于 Promise then 后的回调代码,会让 Event Loop 分配事件池。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(async function(){
const p4 = Promise.reject('error') // rejected status
try {
const res = await p4
console.log(res)
} catch (err){
console.error(err)
}
})()

(async function() {
const p5 = Promise.reject('err1')
const awaitp5 = await p5 // 在这里就会抛出错误,下面的代码不会执行,除非用 try catch 去捕获异常
console.log(awaitp5)
})()
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
// test 1
async function fn() {
return 100 // 相当于 return Promise.resolve(100)
}
(async function () {
const a = fn() // Promise<完成态:100>>
const b = await fn() // 100
})()

// test 2
(async function () {
console.log('start')
const a = await 100
console.log('a', a)
const b = await Promise.resolve(200)
console.log('b', b)
const c = await Promise.reject(300)
console.log('c', c)
console.log('end')
})()

// test 3
async function async1 () {
console.log('async1 start') // 2
await async2()
console.log('async1 end') // 5
}
async function async2 () {
console.log('async2') // 3
}
console.log('script start') // 1
async1()
console.log('script end') // 4


// test 4
async function async1 () {
console.log('async1 start') // 2
await async2() //
console.log('async1 end') // 6
}
async function async2 () {
console.log('async2') // 3
}
console.log('script start') // 1
setTimeout (function() { // 下次宏任务 首先执行
console.log('setTimeout') // 8
}, 0)
async1()
new Promise(function(resolve) { // 当次 微任务
console.log('promise1') // 4
resolve()
}).then(function() {
console.log('promise2') // 7
})
console.log('script end') // 5
前端使用异步的场景有哪些?

网络请求,ajax 、图片加载

定时任务,如 setTimeout

1
2
3
4
5
6
7
8
// img load
console.log('start')
let img = document.createElement('img')
img.onload = function() {
console.log('loaded')
}
img.src = '/xxx.png'
console.log('end')
请描述 event loop (事件循环 / 事件轮询)的机制,可画图

event loop 是异步回调的原理

包含三个概念:
callStack: 执行栈。不论是同步还是异步,放进来 callStack 就开始执行
callback Queue: 异步队列。 异步方法都往这里放。
Event Loop:事件轮询器。一个 JS 自带处理事件归属的机制,当 callStack 为空时,Event Loop 开始从 callback Queue 里拿出事件,并放进 callStack里。然后继续等 callStack 处理,执行完又继续轮询。

注意:
setTimeout(fn,0)的含义是,指定某个任务在 callStack 最早可得的空闲时间执行,也就是说,尽可能早执行。它在 callback Queue 的尾部添加一个事件,因此要等到 callStack 和 callback Queue 现有的事件都处理完,才会得到执行。

EventLoop

DOM 事件和 Event loop的理解

DOM 事件在没触发时,是存储在哪里?
触发后,存储在 callback Queue 里,通过 Event loop 来处理。
除了异步事件以外,还包括一些用户产生的事件(比如鼠标点击、页面滚动等等)。只要指定过回调函数,这些事件发生时就会进入 callback Queue ,等待主线程读取。

执行顺序:
(1)callStack 执行并清空
(2)从 微任务队列 micro task queue 里,获取微任务,执行
(3)在 DOM 有修改的情况下,尝试 DOM 渲染
(4)触发 Event loop

JS 如何执行

从前到后,一行一行执行
如果某行执行报错,则停止下面代码的执行
先执行同步代码,再执行异步

宏任务 和 微任务 分别是什么,区别是什么?

宏任务: setTimeout 、 setInterval 、 Ajax 、 DOM事件 ( 浏览器规定的 )
微任务: Promise 、 async / await ( ES6 语法 规定的 )

宏任务 是 在 DOM 渲染后 触发
微任务 是 在 DOM 渲染前 触发

微任务执行时机 比 宏任务 要早

promise 和 setTimeout 的执行顺序:(与 宏任务 微任务 有关)
1
2
3
4
5
6
7
8
console.log(100)
setTimeout(() => {
console.log(200)
})
Promise.resolve().then(() => {
console.log(300)
})
console.log(400)
for … in 和 for … of

for … in (以及 forEach for )是常规的同步遍历
for … of 常用于异步的遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// setTimeout 返回自乘 模拟异步
async function plusNum(num) {
return new Promise(resolve => {
setTimeout(() => {
resolve(num * num)
}, 1000)
})
}

const testArr = [1, 2, 3]
testArr.forEach(async (item) => {
console.log(await plusNum(item)) // 1 4 9 结果是同时出现 也是所有异步都同时调用了
})

for await (let i of testArr) {
console.log(await plusNum(i)) // 1 4 9 会同步的方式 执行所有await 会等待上次结果结束
}

在另一篇博文有详细介绍 异步知识,链接:点此
在另一篇博文有详细介绍 JS 执行知识,链接:点此

JS Web API

在另一篇博文有详细介绍 JS Web API,链接:点此

事件

在另一篇博文有详细介绍 事件,链接:点此

参考链接:
MDN Array-like slice

运行环境

在另一篇博文有详细介绍 运行环境,链接 点此

  • 本文标题:JS_review
  • 本文作者:Jonnzer
  • 创建时间:2020-04-12 09:30:00
  • 本文链接:https://jonnzer.github.io/2020/04/12/JS/JS_review/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论