动作函数的执行上下文(this 指向)是如何处理的?
误解
在 C4 前端交流会上播放的 幻灯片 里有这样一段代码:
$body.on('click','[data-action]',function(){varactionName=$(this).data('action')varaction=actionList[actionName]if($.isFunction(action))action()})估计很多同学看到这里,可能会误以为这就是 Action 的实现。实际上幻灯片里的所有代码都只是示意性的——出于对演示效果的考虑,过滤了很多细节。
Action 的实现
在初始化阶段,Action 的统一绑定是这样做的(简化代码):
$body.on('click','[data-action]',function(){// get `actionName`// ..._handle(actionName,this)})可以看到当前点击事件的触发元素(this)会被传递给 _handle() 函数。而 _handle() 在调用动作函数时是这样做的(简化代码):
function_handle(actionName,context){varfn=_actionList[actionName]if(fn&&$.isFunction(fn)){returnfn.call(context||window)}}请注意这个 fn.call(context || window),动作元素会成为动作函数的执行上下文。
因此,在编写动作函数时,不需要特意考虑上下文绑定的事情,其内部的 this 总是会指向触发动作的元素(这跟事件回调函数的执行上下文是类似的,符合开发习惯)。
关于 .trigger() 方法
对于这些在内部使用了 this 的动作函数,直接调用会出现 this 指向错误的问题。因此,.trigger() 方法在设计时,就允许传入第二个参数,用于在触发指定动作函数时指定执行上下文。
但这只是一条万不得已的后路,在实际开发中应该避免大量使用。两点原因:
- 每次都要找到合适的函数执行上下文传进去,不方便。写出来的代码也必然是不够清爽的。
- 理论上需要在 JS 中触发的动作,应该是与 DOM 元素无关的,或者不是那么严格相关的(即是否把一个 DOM 元素作为执行上下文传进去不应该影响到动作执行的正确性)。
如果确实需要在 JS 层面对某个特定元素触发特定动作(通常在 HTML 中这个元素已经声明了自己的动作),我会建议这样做:
$('[data-action="my-action"]').first().trigger('click')原理是利用 JS 来模拟用户的点击行为,后面的事情就自然而然地发生了。这样代码的表现力也会更好。
动作函数的执行上下文(
this指向)是如何处理的?误解
在 C4 前端交流会上播放的 幻灯片 里有这样一段代码:
估计很多同学看到这里,可能会误以为这就是 Action 的实现。实际上幻灯片里的所有代码都只是示意性的——出于对演示效果的考虑,过滤了很多细节。
Action 的实现
在初始化阶段,Action 的统一绑定是这样做的(简化代码):
可以看到当前点击事件的触发元素(
this)会被传递给_handle()函数。而_handle()在调用动作函数时是这样做的(简化代码):请注意这个
fn.call(context || window),动作元素会成为动作函数的执行上下文。因此,在编写动作函数时,不需要特意考虑上下文绑定的事情,其内部的
this总是会指向触发动作的元素(这跟事件回调函数的执行上下文是类似的,符合开发习惯)。关于
.trigger()方法对于这些在内部使用了
this的动作函数,直接调用会出现this指向错误的问题。因此,.trigger()方法在设计时,就允许传入第二个参数,用于在触发指定动作函数时指定执行上下文。但这只是一条万不得已的后路,在实际开发中应该避免大量使用。两点原因:
如果确实需要在 JS 层面对某个特定元素触发特定动作(通常在 HTML 中这个元素已经声明了自己的动作),我会建议这样做:
原理是利用 JS 来模拟用户的点击行为,后面的事情就自然而然地发生了。这样代码的表现力也会更好。