面对 this 指向丢失,尤雨溪在 Vuex 源码中是怎么处理的

1. 前言

大家好,我是若川。好久以前我有写过《面试官问系列》,旨在帮助读者提升JS基础知识,包含new、call、apply、this、继承相关知识。其中写了 面试官问:this 指向 文章。在掘金等平台收获了还算不错的反馈。

最近有小伙伴看我的 Vuex源码 文章,提到有一处this指向有点看不懂(好不容易终于有人看我的源码文章了,感动的要流泪了^_^)。于是我写篇文章答疑解惑,简单再说说 this 指向和尤大在 Vuex 源码中是怎么处理 this 指向丢失的。

2. 对象中的this指向

var person = {name: '若川',say: function(text){console.log(this.name + ', ' + text);}
}
console.log(person.name);
console.log(person.say('在写文章')); // 若川, 在写文章
var say = person.say;
say('在写文章'); // 这里的this指向就丢失了,指向window了。(非严格模式)

3. 类中的this指向

3.1 ES5

// ES5
var Person = function(){this.name = '若川';
}
Person.prototype.say = function(text){console.log(this.name + ', ' + text);
}
var person = new Person();
console.log(person.name); // 若川
console.log(person.say('在写文章'));
var say = person.say;
say('在写文章'); // 这里的this指向就丢失了,指向 window 了。

3.2 ES6

// ES6
class Person{construcor(name = '若川'){this.name = name;}say(text){console.log(`${this.name}, ${text}`);}
}
const person = new Person();
person.say('在写文章')
// 解构
const { say } = person;
say('在写文章'); // 报错 this ,因为ES6 默认启用严格模式,严格模式下指向 undefined

4. 尤大在Vuex源码中是怎么处理的

先看代码

class Store{constructor(options = {}){this._actions = Object.create(null);// bind commit and dispatch to self// 给自己 绑定 commit 和 dispatchconst store = thisconst { dispatch, commit } = this// 为何要这样绑定 ?// 说明调用commit和dispach 的 this 不一定是 store 实例// 这是确保这两个函数里的this是store实例this.dispatch = function boundDispatch (type, payload) {return dispatch.call(store, type, payload)}this.commit = function boundCommit (type, payload, options) {return commit.call(store, type, payload, options)}}dispatch(){console.log('dispatch', this);}commit(){console.log('commit', this);}
}
const store = new Store();
store.dispatch(); // 输出结果 this 是什么呢?const { dispatch, commit } = store;
dispatch(); // 输出结果 this 是什么呢?
commit();  // 输出结果 this 是什么呢?
输出结果截图

结论:非常巧妙的用了calldispatchcommit函数的this指向强制绑定到store实例对象上。如果不这么绑定就报错了。

4.1 actions 解构 store

其实Vuex源码里就有上面解构const { dispatch, commit } = store;的写法。想想我们平时是如何写actions的。actions中自定义函数的第一个参数其实就是 store 实例。

这时我们翻看下actions文档https://vuex.vuejs.org/zh/guide/actions.html

const store = new Vuex.Store({state: {count: 0},mutations: {increment (state) {state.count++}},actions: {increment (context) {context.commit('increment')}}
})

也可以用解构赋值的写法。

actions: {increment ({ commit }) {commit('increment')}
}

有了Vuex源码构造函数里的call绑定,这样this指向就被修正啦~不得不说祖师爷就是厉害。这一招,大家可以免费学走~

接着我们带着问题,为啥上文中的context就是store实例,有dispatchcommit这些方法呢。继续往下看。

4.2 为什么 actions 对象里的自定义函数 第一个参数就是 store 实例。

以下是简单源码,有缩减,感兴趣的可以看我的文章 Vuex 源码文章

class Store{construcor(){// 初始化 根模块// 并且也递归的注册所有子模块// 并且收集所有模块的 getters 放在 this._wrappedGetters 里面installModule(this, state, [], this._modules.root)}
}

接着我们看installModule函数中的遍历注册 actions 实现

function installModule (store, rootState, path, module, hot) {// 省略若干代码// 循环遍历注册 actionmodule.forEachAction((action, key) => {const type = action.root ? key : namespace + keyconst handler = action.handler || actionregisterAction(store, type, handler, local)})
}

接着看注册 actions 函数实现 registerAction

/**
* 注册 mutation
* @param {Object} store 对象
* @param {String} type 类型
* @param {Function} handler 用户自定义的函数
* @param {Object} local local 对象
*/
function registerAction (store, type, handler, local) {const entry = store._actions[type] || (store._actions[type] = [])// payload 是actions函数的第二个参数entry.push(function wrappedActionHandler (payload) {/*** 也就是为什么用户定义的actions中的函数第一个参数有*  { dispatch, commit, getters, state, rootGetters, rootState } 的原因* actions: {*    checkout ({ commit, state }, products) {*        console.log(commit, state);*    }* }*/let res = handler.call(store, {dispatch: local.dispatch,commit: local.commit,getters: local.getters,state: local.state,rootGetters: store.getters,rootState: store.state}, payload)// 源码有删减
}

比较容易发现调用顺序是 new Store() => installModule(this) => registerAction(store) => let res = handler.call(store)

其中handler 就是 用户自定义的函数,也就是对应上文的例子increment函数。store实例对象一路往下传递,到handler执行时,也是用了call函数,强制绑定了第一个参数是store实例对象。

actions: {increment ({ commit }) {commit('increment')}
}

这也就是为什么 actions 对象中的自定义函数的第一个参数是 store 对象实例了。

好啦,文章到这里就基本写完啦~相对简短一些。应该也比较好理解。

最后再总结下 this 指向

摘抄下面试官问:this 指向文章结尾。

如果要判断一个运行中函数的 this 绑定, 就需要找到这个函数的直接调用位置。找到之后 就可以顺序应用下面这四条规则来判断 this 的绑定对象。

  1. new 调用:绑定到新创建的对象,注意:显示return函数或对象,返回值不是新创建的对象,而是显式返回的函数或对象。

  2. call 或者 apply( 或者 bind) 调用:严格模式下,绑定到指定的第一个参数。非严格模式下,nullundefined,指向全局对象(浏览器中是window),其余值指向被new Object()包装的对象。

  3. 对象上的函数调用:绑定到那个对象。

  4. 普通函数调用:在严格模式下绑定到 undefined,否则绑定到全局对象。

ES6 中的箭头函数:不会使用上文的四条标准的绑定规则, 而是根据当前的词法作用域来决定this, 具体来说, 箭头函数会继承外层函数,调用的 this 绑定( 无论 this 绑定到什么),没有外层函数,则是绑定到全局对象(浏览器中是window)。这其实和 ES6 之前代码中的 self = this 机制一样。


最近组建了一个江西人的前端交流群,如果你是江西人可以加我微信 ruochuan12 私信 江西 拉你进群。


推荐阅读

我在阿里招前端,该怎么帮你(可进面试群)
我读源码的经历

在字节做前端一年后,有啥收获~
老姚浅谈:怎么学JavaScript?

················· 若川简介 ·················

你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》多篇,在知乎、掘金收获超百万阅读。
从2014年起,每年都会写一篇年度总结,已经写了7篇,点击查看年度总结。
同时,活跃在知乎@若川,掘金@若川。致力于分享前端开发经验,愿景:帮助5年内前端人走向前列。

识别方二维码加我微信、长期交流学习

今日话题

略。欢迎分享、收藏、点赞、在看我的公众号文章~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/275618.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

转:Python: threading.local是全局变量但是它的值却在当前调用它的线程当中

原文地址:http://www.cnblogs.com/fengmk2/archive/2008/06/04/1213958.html 在threading module中,有一个非常特别的类local。一旦在主线程实例化了一个local,它会一直活在主线程中,并且又主线程启动的子线程调用这个local实例时&#xff0c…

matlab的边缘检测方法,常用图像边缘检测方法及Matlab研究

2011 年 2 月 15 日第 34 卷第 4 期 现代电子技术 Modern Electronics Technique Feb. 2011 Vol. 34 No. 4 常用图像边缘检测方法及 Matlab 研究 韦  炜 (西安文理学院 , 陕西 西安   710065) 摘  要 :边缘检测在数字图像处理中有着重要的作用。为了在实际应用中能够选择最佳…

单选按钮步骤流程向导 js_创建令人愉快的按钮的6个步骤

单选按钮步骤流程向导 jsThere is no modern interactive UI without buttons. They are an fundamental part of every digital solution. Learn how to improve the style of your buttons and delight users with perfect style.没有按钮,就没有现代的交互式UI。…

Android 四大组件之 Activity

什么是 Activity? Activity 是 Android 的四大组件之一,是用户操作的可视化界面,它为用户提供了一个完成操作指令的窗口。 当我们创建完 Activity 之后,需要调用 setContentView(view) 方法来完成界面的显示,以此来为用…

axios怎么封装,才能提升效率?

大家好,我是若川。今天分享一篇axios封装的文章。学习源码系列、面试、年度总结、JS基础系列。作为前端开发者,每个项目基本都需要和后台交互,目前比较流行的ajax库就是axios了,当然也有同学选择request插件,这个萝卜白…

图片有花

http://www.56.com/u50/v_NTUwMzE1NDM.html http://www.56.com/u39/v_NTUwMzE2MjA.html http://www.cnblogs.com/coffee_cn/archive/2009/11/30/1613823.html http://www.imagemagick.org/script/binary-releases.php?ImageMagickmm3e9bn5mtos6eiaelh9d4aoe4#windows 转载于:h…

java 代码执行el,专属于java的漏洞——EL表达式注入

前言“FSRC经验分享”系列文章,旨在分享焦点科技信息安全部工作过程中的经验总结,包括但不限于漏洞分析、运营技巧、sdl推行、等保合规、自研工具等等。欢迎各位安全从业者持续关注~0x01EL简介表达式语言(Expression Language 以下简称EL)是以JSTL(JavaS…

护肤产生共鸣_通过以人为本的设计编织共鸣的20个指针

护肤产生共鸣Deep into a project right now, I can’t help but reflect on how I practice empathy in design. Human centered design means empathising with and designing for people, keeping our focus on people throughout. It is not just one stage, it is a minds…

谷歌已推送 Android Q Beta 1

开发四年只会写业务代码,分布式高并发都不会还做程序员? >>> 今日凌晨,谷歌正式推送了 Android Q 的首个 Beta 版本,Pixel 全系列手机可以尝鲜体验这款最新的系统。 据官方博客介绍,Android Q 为用户带来了…

对使用CodeSmith模板生成NHibernate的代码的分析

CodeSmith是我们常用的代码生成工具,其跟据不同的模板生成不同代码的方式能大大加快我们的项目开发,减少重复劳动。NHibernate模板就是其常用模板之一。从这里可以下载到最新的模板文件。现在最新的版本为NHibernate-v1.2.1.2125,可以生成NHi…

若川诚邀你加源码共读群,每周一起学源码

小提醒:若川视野公众号面试、源码等文章合集在菜单栏中间【源码精选】按钮,欢迎点击阅读,也可以星标我的公众号,便于查找。回复pdf,可以获取前端优质书籍。最近我创建了一个源码共读的前端交流群,希望尝试帮…

matlab 规范,matlab-代码-规范

matlab-代码-规范 1. 标识符命名原则 标识符的名字应当直观,其长度应当符合“最小长度,最大信息量”原则。 1) 非矩阵变量: 变量名应该以小写字母开头的大小写混合形式 譬如:shadowFadingTable,servingSector&#xf…

zoom视频会议官网_人性化视频会议的空间(Zoom等)

zoom视频会议官网第二部分:房间的创造力 (Part Two: The Creativity of Rooms) In Part One I shared thoughts on how virtual spaces can often leave little room to embody our most human selves. The lack of a public sphere that parallels our shared publ…

KOFLive Postmortem

为期两个月的团队项目完成了,我们的游戏也已经发布。在这个名叫KOFLive的小游戏里,我们集成了五个真人角色,每个角色有拳脚基本招数以及三个小招、一个大招,硬值、防御、集气、双人对战、人机对战、练习模式等格斗游戏的Feature基…

单调队列优化多重背包

就是按照 % 体积的余数来分组&#xff0c;每组单调队列优化。 直接上模板好了。 1 #include <bits/stdc.h>2 3 typedef long long LL;4 const int N 100010;5 6 int n, V, cnt[N], cost[N];7 LL f[2][N], val[N], p[N], top, head;8 9 inline void Max(LL &a, const…

2021年7月 虾皮、货拉拉、有赞等面经总结

大家好&#xff0c;我是若川&#xff0c;加我微信 ruochuan12 进源码交流群。今天分享一篇7月份新鲜出炉的面经&#xff0c;文章较长&#xff0c;可以收藏再看。学习源码系列、面试、年度总结、JS基础系列。本文来自作者几米阳光 投稿 原文链接&#xff1a;https://juejin.cn/p…

Oracle对表名大小写敏感吗,让Oracle 大小写敏感 表名 字段名 对像名

一、解决方案1、在表名、字段名、对象名上加上双引号&#xff0c;即可实现让oracle大小写区分。2、但是这又引起了另一个问题&#xff1a;在数据库操作中&#xff0c;sql语句中相应的表名、字段名、对象名上一定要加双引号。解决办法是&#xff1a;使用"\"转义。如&a…

谷歌抽屉_Google(最终)会杀死导航抽屉吗?

谷歌抽屉A couple of months ago Google has celebrated with enthusiasm 15 years of Google Maps, one of the most used and appreciated services worldwide from the company.几个月前&#xff0c;Google热情地庆祝Google Maps诞生15周年&#xff0c;这是该公司在全球范围…

MySQL——安装

MySQL——安装 1. 下载源&#xff1a; http://repo.mysql.com/yum/mysql-8.0-community/el/7/x86_64/mysql80-community-release-el7-2.noarch.rpm 该源目前为8.0版本&#xff0c;如果需要最新请退至根目录找。 1wget http://repo.mysql.com/yum/mysql-8.0-community/el/7/x86_…

写给初中级前端的高级进阶指南等

大家好&#xff0c;我是若川。话不多说&#xff0c;这一次花了几小时精心为大家挑选了20余篇好文&#xff0c;供大家阅读学习。本文阅读技巧&#xff0c;先粗看标题&#xff0c;感兴趣可以都关注一波&#xff0c;绝对不亏。程序员成长指北考拉妹子&#xff0c;一个有趣的且乐于…