javascript函数的闭包现象(Javascript初级知识点原型)

值类型存储在栈内存中,引用类型存储在堆内存中,今天小编就来聊一聊关于javascript函数的闭包现象?接下来我们就一起去研究一下吧!

javascript函数的闭包现象(Javascript初级知识点原型)

javascript函数的闭包现象

JS变量类型和计算
  • typeof 能判断出哪些类型
  • 何时使用 === 何时使用 ==
  • 值类型和引用类型的区别
  • 手写深拷贝
值类型和引用类型

值类型存储在栈内存中,引用类型存储在堆内存中

// 值类型 let a = 100 let b = a a = 200 console.log(b) // 100

// 引用类型 let a = { age: 20 } let b = a b.age = 21 console.log(a.age) // 21

常见值类型和引用类型

// 常见值类型 let u // undefined const s = 'abc' const n = 100 const b = true const s = Symbol('s')

// 常见引用类型 const obj = { x: 100 } const arr = ['a', 'b', 'c'] const n = null // 特殊引用类型,指针指向为空地址 // 特殊的引用类型,但不用于存储数据,所以没有拷贝、复制函数这一说 Function fun() {}

类型判断

typeof 运算符

  • 识别所有值类型
  • 识别函数
  • 判断是否是引用类型(不可再细分)

深克隆

function deepClone(obj = {}) { if (typeof obj !== 'object' || obj === null) { // obj 是 null,或是不是对象和数组,直接返回 return obj } // 初始化返回结果 let result if (obj instanceof Array) { result = [] } else { result = {} } for (let key in obj) { // 保证 key 不是原型的属性 if (obj.hasOwnProperty(key)) { // 递归 result[key] = deepClone(obj[key]) } } return result }

类型转换

  • 字符串拼接
  • ==
  • if 语句逻辑运算

const a = 100 10 // 110 const b = 100 '10' // 10010 const c = true '10' // true10 const d = 100 parseInt('10') // 110

==

100 == '100' // true 0 == '0' // true 0 == false // true false == '' // true null == undefined // true NaN == NaN // false // 除了 == null 之外,其他都一律用 === ,例如: const obj = { x: 100 } if (obj.a == null) {} // 相当于 if (obj.a === null || obj.a === undefined) {}

逻辑运算

if 语句和逻辑运算

  • truly 变量:!!a === true 的变量
  • falsely 变量:!!a === false 的变量
原型和原型链
  • 如何判断一个变量是不是数组?
  • 手写一个简易的 jQuery,考虑插件和扩展性
  • class 的原型本质,怎么理解?
class

class Student { constructor(name, number) { this.name = name this.number = number } greeting() { console.log(`Hello, My name is ${this.name}, number is ${this.number}`) } } // 实例化 const zhangsan = new Student('zhangsan', 1) zhangsan.greeting() // Hello, My name is zhangsan, number is 1

继承

// 父类 class Person { constructor(name) { this.name = name } eat() { console.log(`${this.name} eat something`) } } // 子类 class Student extends Person { constructor(name, number) { super(name) this.number = number } greeting() { console.log(`Hello, My name is ${this.name}, number is ${this.number}`) } } // 子类 class Teacher extends Person { constructor(name, subject) { super(name) this.subject = subject } teach() { console.log(`My name is ${this.name}, and I am teaching ${this.subject}`) } } // 实例化 const zhangsan = new Student('zhangsan', 1) zhangsan.greeting() // Hello, My name is zhangsan, number is 1 zhangsan.eat() // zhangsan eat something const mrWang = new Teacher('wang', 'Math') mrWang.eat() // wang eat something mrWang.teach() // My name is wang, and I am teaching Math // 类型判断 console.log(zhangsan instanceof Student) // true console.log(zhangsan instanceof Person) // true console.log(zhangsan instanceof Object) // true // true console.log([] instanceof Array) console.log([] instanceof Object) console.log({} instanceof Object)

原型

// class 实际上是函数,语法糖而已 typeof Person // function typeof Student // function // 隐式原型和显示原型 console.log(zhangsan.__proto__) console.log(Student.prototype) console.log(zhangsan.__proto__ === Student.prototype) // true

原型关系

  • 每个 class 都有显示原型 prototype
  • 每个实例都有隐式原型 __ proto__
  • 实例的 __ proto__指向对应的 class 的 prototype
原型链

console.log(Student.prototype.__proto__) console.log(Person.prototype) console.log(Person.prototype === Student.prototype.__proto__) // true

instanceof

手动实现 instanceof

function myInstanceof(left, right) { // 获取类的原型 let prototype = right.prototype // 获取对象的原型 left = left.__proto__ // 判断对象的类型是否等于类型的原型 while (true) { if (left === null) return false if (prototype === left) return true left = left.__proto__ } }

手写一个简易的 jQuery,考虑插件和扩展性

class jQuery { constructor(selector) { const result = document.querySelectorAll(selector) const length = result.length for (let i = 0; i < length; i ) { this[i] = result[i] } this.length = length this.selector = selector } get(index) { 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) }) } } // 插件 jQuery.prototype.dialog = function (info) { alert(info) } // “造轮子” class myJQuery extends jQuery { constructor(selector) { super(selector) } // 扩展自己的方法 addClass(className) { // ... } style(data) { // ... } }

作用域和闭包
  • this 的不同应用场景,如何取值?
  • 手写 bind 函数
  • 实际开发中闭包的应用场景,举例说明
  • 创建 10 个 <a> 标签点击的时候弹出对应的序号
作用域

let a = 0 function fn1() { let a1 = 100 function fn2() { let a2 = 200 function fn3() { let a3 = 300 return a a1 a2 a3 } fn3() } fn2() } fn1()

  • 全局作用域
  • 函数作用域
  • 块级作用域(ES6新增)
自由变量
  • 一个变量在当前作用域没有定义,但被使用了
  • 向上级作用域,一层一层依次寻找,直到找到了为止
  • 如果在全局作用域都没有找到,则报错 xxx is not defined
闭包
  • 作用域应用的特殊情况,有两种表现
  • 函数作为参数被传递
  • 函数作为值返回

// 函数作为返回值 function create() { let a = 100 return function () { console.log(a) } } let fn = create() let a = 200 fn() // 100 // 函数作为参数 function print(fn) { let a = 200 fn() } let a = 100 function fn() { console.log(a) } print(fn) // 100

闭包:自由变量的查找,是在函数定义的地方,向上级作用域查找

不是在执行的地方!!!

this
  • 作为普通函数
  • 使用 call apply bind
  • 作为对象的方法被调用
  • 在 class 方法中调用
  • 箭头函数

this 取什么值是在函数执行的时候确定的,不是在定义的时候

function fn1() { console.log(this) } fn1() // window fn1.call({x: 100}) // {x: 100} const fn2 = fn1.bind({x: 200}) fn2() // {x: 200}

箭头函数

const zhangsan = { name: 'zhangsan', greeting() { // this 即当前对象 console.log(this) }, wait() { setTimeout(function() { // this === window console.log(this) }) } } // 箭头函数的 this 永远取上级作用域的 this const zhangsan = { name: 'zhangsan', // this 即当前对象 greeting() { console.log(this) }, wait() { setTimeout(() => { // this 即当前对象 console.log(this) }) } }

创建 10 个 <a> 标签点击的时候弹出对应的序号

let a for (let i = 0; i < 10; i ) { a = document.createElement('a') a.innerHTML = i '<br>' a.addEventListener('click', function(e) { e.preventDefault() alert(i) }) document.body.appendChild(a) }

手写 bind 函数

Function.prototype.myBind = function() { // 将参数拆解为数组 const args = Array.prototype.slice.call(arguments) // 获取 this(数组第一项) const that = args.shift() // fn.bind(...) 中的 fn const self = this // 返回一个函数 return function() { return self.apply(that, args) } }

实际开发中闭包的应用

  • 隐藏数据
  • 如做一个简单的 cache 工具
  • function createCache() { const data = {} // 闭包中的数据,被隐藏,不被外界访问 return { set(key, value) { data[key] = value }, get(key) { return data[key] } } } const c = createCache() c.set('a', 100) console.log(c.get('a'))
异步
  • 同步和异步得区别是什么?
  • 手写 Promise 加载一张图片
  • 前端使用异步的场景
  • 请描述 event loop (事件循环/事件轮询)的机制,可画图
  • 什么是宏认为和微任务,两者有什么区别?
  • Promise 有哪三种状态?如何变化?
  • Promise 的 then 和 catch 的连接问题
  • async/await 语法
  • Promise 和 setTimeout 的顺序问题
单线程
  • JS 是单线程语言,只能同时做一件事
  • 浏览器和 nodejs 已支持 JS 启动进程,如 Web Worker
  • JS 和 DOM 渲染共用同一个线程,因为 JS 可修改 DOM 结构
  • 异步基于 回调 callback 函数形式
callback

// 异步 console.log(100) setTimeout(function() { console.log(200) }, 1000) console.log(300) // 同步 console.log(100) alert(200) console.log(300)

异步不会阻塞代码执行

同步会阻塞代码执行

应用场景
  • 网络请求,如 ajax 图片加载
  • 定时任务,如 setTimeout
promise

基本使用

// 加载图片 function loadImg(src) { const p = new Promise( (resolve, reject) => { const img = document.createElement('img') img.onload = () => { resolve(img) } img.onerror = () => { const err = new Error(`图片加载失败 ${src}`) reject(err) } img.src = src } ) return p } const url = 'www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png' loadImg(url).then(img => { console.log(img.width) return img }).then(img => { console.log(img.height) }).catch(ex => console.error(ex))

状态

  • 三种状态 pending resolved rejected 变化: pending => resolved 或 pending => rejected 变化是不可逆的
  • 状态的表现和变化 pending 状态,不会触发 then 和 catch resolved 状态,会触发后续的 then 回调函数 rejected 状态,会触发后续的 catch 回调函数

const p1 = new Promise((resolve, reject) => {}) console.log(p1) // pending const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve() }) }) console.log(p2) // pending 一开始打印时 setTimeout(() => console.log(p2)) // resolved const p3 = new Promise((resolve, reject) => { setTimeout(() => { reject() }) }) console.log(p3) // pending 一开始打印时 setTimeout(() => console.log(p3)) // rejected // 直接获取一个 resolved 状态的 Promise const resolved = Promise.resolve(100) console.log(resolved) // resolved // 直接获取一个 rejected 状态的 Promise const resolved = Promise.reject('err') console.log(resolved) // rejected

  • then 和 catch 对状态的影响 then 正常返回 resolved ,里面有报错则返回 rejected catch正常返回 resolved ,里面有报错则返回 rejected

// then() 一般正常返回 resolved 状态的 promise Promise.resolve().then(() => { return 100 }) // then() 里抛出错误,会返回 rejected 状态的 promise Promise.resolve().then(() => { throw new Error('err') }) // catch() 不抛出错误,会返回 resolved 状态的 promise Promise.reject().catch(() => { console.error('catch some error') }) // catch() 抛出错误,会返回 rejected 状态的 promise Promise.reject().catch(() => { console.error('catch some error') throw new Error('err') })

// 第一题 Promise.resolve().then(() => { console.log(1) }).catch(() => { console.log(2) }).then(() => { console.log(3) }) // 第二题 Promise.resolve().then(() => { // 返回 rejected 状态的 promise console.log(1) throw new Error('erro1') }).catch(() => { // 返回 resolved 状态的 promise console.log(2) }).then(() => { console.log(3) }) // 第三题 Promise.resolve().then(() => { // 返回 rejected 状态的 promise console.log(1) throw new Error('erro1') }).catch(() => { // 返回 resolved 状态的 promise console.log(2) }).catch(() => { console.log(3) })

event-loop
  • JS 是单线程运行的
  • 异步要基于回调来实现
  • event loop 就是异步回调的实现原理

JS 如何执行的

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

event loop 执行过程

  • 同步代码,一行一行放在 Call Stack 执行
  • 遇到异步,会先 “记录” 下,等待时机(定时,网络请求)
  • 时机到了,就移动到 Callback Queue
  • 如果 Call Stack 为空(即同步代码执行完)Event Loop 开始工作
  • 轮询查找 Callback Queue ,如果有则移动到 Call Stack 执行
  • 然后继续轮询查找(永动机一样)

DOM 事件和 event loop

  • 异步(setTimeout,ajax 等)使用回调,基于 event loop
  • DOM 事件也使用回调,基于 event loop
async/await
  • 异步回调 callback hell
  • Promise 基于 then catch 链式调用,但也是基于回调函数
  • async/await 是同步语法,彻底消灭回调函数

有很多 async 的面试题,例如

  • async 直接返回,是什么
  • async 直接返回 promise
  • await 后面不加 promise

基本语法

function loadImg(src) { const promise = new Promise((resolve, reject) => { const img = document.createElement('img') img.onload = () => { resolve(img) } img.onerror = () => { reject(new Error(`图片加载失败 ${src}`)) } img.src = src }) return promise } async function loadImg1() { const src1 = 'www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png' const img1 = await loadImg(src1) return img1 } async function loadImg2() { const src2 = 'https://avatars3.githubusercontent.com/u/9583120' const img2 = await loadImg(src2) return img2 } (async function () { // 注意:await 必须放在 async 函数中,否则会报错 try { // 加载第一张图片 const img1 = await loadImg1() console.log(img1) // 加载第二张图片 const img2 = await loadImg2() console.log(img2) } catch (ex) { console.error(ex) } })()

async/await 和 promise 的关系

  • async/await 是消灭异步回调的终极武器
  • 但和 promise 并不互斥
  • 两者反而相辅相成
  • await 相当于 promise 的 then
  • try...catch 可捕获异常,代替了 promise 的 catch
  • async 函数返回结果都是 Promise 对象(如果函数内没返回 Promise ,则自动封装一下)

async function fn2() { return new Promise(() => {}) } console.log( fn2() ) async function fn1() { return 100 } console.log( fn1() ) // 相当于 Promise.resolve(100)

  • await 后面跟 Promise 对象:会阻断后续代码,等待状态变为 resolved ,才获取结果并继续执行
  • await 后续跟非 Promise 对象:会直接返回

(async function () { const p1 = new Promise(() => {}) await p1 console.log('p1') // 不会执行 })() (async function () { const p2 = Promise.resolve(100) const res = await p2 console.log(res) // 100 })() (async function () { const res = await 100 console.log(res) // 100 })() (async function () { const p3 = Promise.reject('some err') const res = await p3 console.log(res) // 不会执行 })()

  • try...catch 捕获 rejected 状态

(async function () { const p4 = Promise.reject('some err') try { const res = await p4 console.log(res) } catch (ex) { console.error(ex) } })()

总结来看:

  • async 封装 Promise
  • await 处理 Promise 成功
  • try...catch 处理 Promise 失败

异步本质

await 是同步写法,但本质还是异步调用。

async function async1 () { console.log('async1 start') await async2() console.log('async1 end') // 关键在这一步,它相当于放在 callback 中,最后执行 } async function async2 () { console.log('async2') } console.log('script start') async1() console.log('script end')

即,只要遇到了 await ,后面的代码都相当于放在 callback 里。

for...of

  • for ... in (以及 forEach for)是常规的同步遍历
  • for ... of 常用与异步的循环

// 定时算乘法 function multi(num) { return new Promise((resolve) => { setTimeout(() => { resolve(num * num) }, 1000) }) } // // 使用 forEach ,是 1s 之后打印出所有结果,即 3 个值是一起被计算出来的 // function test1 () { // const nums = [1, 2, 3]; // nums.forEach(async x => { // const res = await multi(x); // console.log(res); // }) // } // test1(); // 使用 for...of ,可以让计算挨个串行执行 async function test2 () { const nums = [1, 2, 3]; for (let x of nums) { // 在 for...of 循环体的内部,遇到 await 会挨个串行计算 const res = await multi(x) console.log(res) } } test2()

微任务/宏任务
  • 宏任务:setTimeout setInterval DOM 事件
  • 微任务:Promise(对于前端来说)
  • 微任务比宏任务执行的更早

console.log(100) setTimeout(() => { console.log(200) }) Promise.resolve().then(() => { console.log(300) }) console.log(400) // 100 400 300 200

event loop 和 DOM 渲染

  • 每一次 call stack 结束,都会触发 DOM 渲染(不一定非得渲染,就是给一次 DOM 渲染的机会!)
  • 然后再进行 event loop

const $p1 = $('<p>一段文字</p>') const $p2 = $('<p>一段文字</p>') const $p3 = $('<p>一段文字</p>') $('#container') .append($p1) .append($p2) .append($p3) console.log('length', $('#container').children().length ) alert('本次 call stack 结束,DOM 结构已更新,但尚未触发渲染') // (alert 会阻断 js 执行,也会阻断 DOM 渲染,便于查看效果) // 到此,即本次 call stack 结束后(同步任务都执行完了),浏览器会自动触发渲染,不用代码干预 // 另外,按照 event loop 触发 DOM 渲染时机,setTimeout 时 alert ,就能看到 DOM 渲染后的结果了 setTimeout(function () { alert('setTimeout 是在下一次 Call Stack ,就能看到 DOM 渲染出来的结果了') })

宏任务和微任务的区别

  • 宏任务:DOM 渲染后再触发
  • 微任务:DOM 渲染前会触发

// 修改 DOM const $p1 = $('<p>一段文字</p>') const $p2 = $('<p>一段文字</p>') const $p3 = $('<p>一段文字</p>') $('#container') .append($p1) .append($p2) .append($p3) // // 微任务:渲染之前执行(DOM 结构已更新) // Promise.resolve().then(() => { // const length = $('#container').children().length // alert(`micro task ${length}`) // }) // 宏任务:渲染之后执行(DOM 结构已更新) setTimeout(() => { const length = $('#container').children().length alert(`macro task ${length}`) })

再深入思考一下:为何两者会有以上区别,一个在渲染前,一个在渲染后?

  • 微任务:ES 语法标准之内,JS 引擎来统一处理。即,不用浏览器有任何关于,即可一次性处理完,更快更及时。
  • 宏任务:ES 语法没有,JS 引擎不处理,浏览器(或 nodejs)干预处理。

经典面试题

async function async1 () { console.log('async1 start') await async2() // 这一句会同步执行,返回 Promise ,其中的 `console.log('async2')` 也会同步执行 console.log('async1 end') // 上面有 await ,下面就变成了“异步”,类似 cakkback 的功能(微任务) } async function async2 () { console.log('async2') } console.log('script start') setTimeout(function () { // 异步,宏任务 console.log('setTimeout') }, 0) async1() new Promise (function (resolve) { // 返回 Promise 之后,即同步执行完成,then 是异步代码 console.log('promise1') // Promise 的函数体会立刻执行 resolve() }).then (function () { // 异步,微任务 console.log('promise2') }) console.log('script end') // 同步代码执行完之后,屡一下现有的异步未执行的,按照顺序 // 1. async1 函数中 await 后面的内容 —— 微任务 // 2. setTimeout —— 宏任务 // 3. then —— 微任务

原创作者:Shadowbringer链接:https://www.jianshu.com/p/17a4a041dc0f

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页