Jacky's blog Jacky's blog
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Jacky Wang

行到水穷处,坐看云起时
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 技术随笔

    • git教程
    • 前端模块化:CommonJS,AMD,CMD,ES6
    • 如何动态加载本地图片
    • GitHub自动部署脚本
    • React生命周期详解
    • 将父组件的props作为组件的初始state的几种方案
    • webpack基础入门
    • StackOver记录:如何在function中使用redux
    • React中使用onClick时的三种性能优化
    • 思考React状态管理
    • forEach/for..in/of
    • styled-components使用记录
    • 深入理解forEach与forof
    • Node技术架构
    • Redux、Mobx状态管理杂谈
    • JS中的类型检测
    • 如何判断图片是否在可视区域
    • 如何使用图片的懒加载操作
    • JavaScript模块化:从CommonJS到AMD
    • git查看:修改用户名、密码
    • 保持数据Immutable的几种写法
    • 如何实现一个Promise.all
    • resolve(Promise对象)会多出两个微任务
    • todo_基于Mocha的测试驱动开发
    • 浏览器页面渲染机制
    • todo-响应式编程之数据劫持
    • todo-immer库的介绍
    • React-Hooks 基础原理解析
    • JS基本功
    • 函数闭包与this指针
      • 0.前言
      • 1. 函数的定义
      • 2.函数的返回值由什么决定?
      • 3.闭包
      • 4.闭包的高级理解及应用
      • 5.this 指针
      • 6.面试题实战
    • 收集 DUMI 配置过程中遇到的问题
    • typescript 类型工具
    • 统一接口的导入导出
    • 网络安全:如何预防 XSS 攻击
    • 浏览器页面渲染机制【2023】
    • 跨域资源请求
    • 浏览器事件循环机制
    • 如何理解浏览器的 user agent 用户代理的含义?
  • 转载文章

  • Mac相关

  • 前端学习周报

  • 包管理工具

  • 技术随笔
  • 技术随笔
wangjiasheng
2022-04-09
目录

函数闭包与this指针

# 0.前言

本篇博客涉及的主要内容涉及 函数调用 以及在javaScript 中难以理解的 this 。

# 1. 函数的定义

一段程序一般由三个部分构成:

  1. 函数:有返回值。(在JavaScript中所有的函数都有返回值)
  2. 过程:无返回值。
  3. 方法:即method,封装在类 或者 对象 中。

上述中的"函数"这个定义需要区别于传统意义上数学函数表达式的概念。在数学中一个函数表达式输入和输出具有明确的关系,即一旦确定自变量x的数组,则因变量y明确。而在 JavaScript 函数代码中,一个函数可以没有明确的输入输出关系,如下:

let x = 0;
function add(){
  x += 1
  return x
}

add() // 1
add() // 2
1
2
3
4
5
6
7
8

当然在设计的时候,我们可以强制保证函数具有明确的输入输出关系,我们将满足此关系的函数代码称为纯函数 代码,将代码内部破坏其 纯函数 特性的行为,称为 副作用 。

# 2.函数的返回值由什么决定?

一段函数代码的返回值是由:调用时的param 以及定义时的env 决定的,见下面这段代码:

let x = "x";
let a = "1";  // 定义时的 env
function f1(x){  
    return x + a  // 由f1函数定义可知,a是环境(env)参数,x是输入(param)参数
};

// 块级作用域
{
    let a = "2";    
    console.log(f1("x")); // 调用时的 param
}
1
2
3
4
5
6
7
8
9
10
11

最终答案为:"x1"

其中, "x" 是调用时的 param,而 a 是定义时的 env。

一般面试题中的难点在于:定义时的 env 不容易确定,并且需要注意的是,f1 在定义时维持住的是 env 环境变量,如果当变量发生改变时,所对应的值也发生变化。如下:

let x = "x";
let a = "1";  
function f1(x){  
    return x + a  
};

let a = "3";
// 块级作用域
{
    let a = "2";    
    console.log(f1("x")); // 答案: "x3"
}
let a = "4" 
1
2
3
4
5
6
7
8
9
10
11
12
13

# 3.闭包

接着上述的案例,正好可以引入到 "闭包" 知识点。

上述函数的 output 取决于两个部分,调用时的param 参数以及 env 参数 。前者基本上我们不会判断出错,问题就在于env 参数。其实在函数定义时,就已将输出"锚定"在定义作用域中了(即,env 环境参数)。

而闭包的核心目的也是如此:使用函数 维护 变量。

最典型的闭包是:

for(var i=0;i<6;i++){
  setTimeout(()=>{console.log(i)},0)
}
// 打印 6 个 6
1
2
3
4

原因是,setTimeout 此函数中的 i 统一取决于其定义环境中的 i 变量,又闭包维护的是 i 变量,而非变量值,因此当值发生变化时,取决于最后一个i 对应的数值。

改进方式:除了传统的将 var 变量改为let 的方案外,我们还可以使用立即执行函数 将env 参数变为param 参数。

for (var i = 0; i < 6; i++) {
    !function(i){
        setTimeout(()=>{
            console.log(i)
        })
    }(i)
}
// 打印 0 1 2 3 4 5 
1
2
3
4
5
6
7
8

# 4.闭包的高级理解及应用

由上述已知,闭包的目的就在于维护一个外部变量(env 参数)。使用 对象 也完全可以实现此效果:

var obj = {
  _i: 0,
  fn(){
    console.log(_i)
  }
}
1
2
3
4
5
6

将其改写为 函数 形式,即为 ”闭包“ :

const handle = function(){
  var i = 0;
  return function(){
    console.log(i)
  }
}
1
2
3
4
5
6

以上,属于 闭包 的基础版本,在实际的案例中,其实闭包使用的非常频繁,如:

  1. 在手写图片懒加载中,使用闭包,维护一个 count 变量统计当前页面 img 标签。

  2. 在节流与防抖函数中,使用闭包,维护 timer 引用地址,或者cur\past 等变量。

    function debounce(fn,delay){
      let timer = null;
      return function(){....}
    }
    
    1
    2
    3
    4
  3. 在 promise.all 中,使用闭包,维护一个 count 变量统计 resolved成功的个数,以及result 数组。

  4. 在 once 函数中,使用闭包,在外部缓存维护:flag 和 result 数组。

    function once(fn){
      let revoked = false;
      return (...args)=>{
        if(revoked) return result;
        revoked = true;
        return fn(...args);
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8

# 5.this 指针

在 JavaScript 中最难理解的部分,也即 this 指针的指向问题。

在下面的函数可以发现,this 等价于env 变量,即箭头函数式如何处理 a 的,就如何处理 this 指针。

// 代码1 
const a = 233
const fn2 = ()=>{console.log(a)}

// 代码2:
console.log(this)
const fn2 = ()=>{console.log(this)}
1
2
3
4
5
6
7

在 JavaScript 中,可以使用 call 、apply 以及 bind 对函数中的 this 进行修改。

网上中有关this 指向的口诀特别多,其实只需要理解下面三种情况下的 this 指针,即可做到以不变应万变:

fn(1,2) 
// fn.call(undefined,1,2); Node 环境
obj.method("hi")
// obj.method.call(obj,"hi")
array[0]("hi")
// array[0].call(array,"hi")
1
2
3
4
5
6

上述注释部分,实际上做了一层 call 改写,将 this 进行显式改写。

注:第1个例子中,直接触发 fn 时,this传入的是当前的全局环境,在 Node 中全局环境是 undefined,在 Chrome中全局环境默认是 windows。

还需要注意的是,一定要区分函数的执行时的调用对象,如下,在编写事件触发函数时,会写以下代码:

button.onclick = function(e){
  console.log(this)
}
1
2
3

此时需要注意的是,上述函数是由用户点击触发的,还是引用触发的。

如果是用户点击触发,即button.onclick(),此时 this 即为 button 对象。

而若是由引用触发,则需要继续讨论:

let obj = {}
obj.f = button.onclick;
obj.f() // 则此时 this 为 obj 对象
1
2
3

# 6.面试题实战

经过上面对函数、闭包以及指针的讲解后,来做一道比较奇葩的面试题。

let length = 10;
function fn(){console.log(this.length)}

let obj = {
  length: 5,
  method(fn){
    fn();
    arguments[0]();
  }
}

obj.method(fn,1)
1
2
3
4
5
6
7
8
9
10
11
12

在 Node 端的运行结果:

undefined
2
1
2

在 Chrome 端运行结果为:

取决于当前页面的 iframe 个数
2
1
2

只需将执行时的function 改写后,即可得到结果:

let obj = {
  length: 5,
  method(fn){
    fn(); // fn.call(undefined|windows)
    arguments[0](); // arguments[0].call(arguments)
  }
}
1
2
3
4
5
6
7

经改写后,fn() 被改写为fn.call(undefined|windows) (在Node 环境中,this传入 undefined,在 Chrome 中,this 传入 windows)

看下 Chrome 在执行阶段的代码:

let length = 10;
funtion(){
  console.log(windows.length);
};
1
2
3
4

注意到上述使用到的定义是 let ,而非var。而前者不会将变量挂载到 windows 对象,而默认指的是当前浏览器的 iframe 标签个数。

arguments[0]() 函数则被解析为arguments[0].call(arguments) 表达式,而此时arguments 为fn 和 1 两个输入参数,此时this.length 则为形参的长度。

编辑 (opens new window)
上次更新: 2022/06/26, 23:06:00
JS基本功
收集 DUMI 配置过程中遇到的问题

← JS基本功 收集 DUMI 配置过程中遇到的问题→

最近更新
01
如何理解浏览器的 user agent 用户代理的含义?
11-05
02
浏览器事件循环机制
10-31
03
浏览器页面渲染机制【2023】
10-15
更多文章>
Theme by Vdoing | Copyright © 2020-2023
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式