blog icon indicating copy to clipboard operation
blog copied to clipboard

call/apply漫谈

Open renaesop opened this issue 8 years ago • 5 comments

在JavaScript中,call/apply是函数原型上的方法,作用是指定函数的context也就是所谓的this变量。JavaScript中this的指向“不明”饱受诟病然而,实际上this的指向十分清晰,this永远指向函数的调用者,而call/apply的作用可以简单地说成了强行更改调用者。

不过,为什么好像其他常用的语言中没有出现this的混淆和call/apply这种函数呢?实际上,门门语言都有这个问题,因为从根本上说,this本身就只能通过当做参数来传入,我们的计算机底层只能调用函数/过程,只是,其他语言都是会有隐式绑定,也就是类似于箭头函数的行为的。

下面是几个语言this实现举例,其中大都涉及到面向对象的实现机制:

Java的实现

嗯,Java是个纯粹的面向对象语言,基于class。使用Java的时候,要显式使用this的场景,只有函数形参或者局部变量和类的成员变量名冲突的时候。 假设我们有这样一个类:

class Hello {
    Hello() {
    }
    public void sayHello () {
        say("Hello, world!");
    }
    private void say(String str) {
        System.out.println(str);
    }
    public static void main(String[] args) {
        Hello hello = new Hello();
        hello.sayHello();
    }
}

在这个简单的java程序中,由静态方法实例化了Hello类,之后调用了实例方法sayHello, 而实例方法sayHello又调用了另一个实例方法say。根据Java虚拟机规范(Java SE第八版本),大致调用过程如下:

  1. 静态方法main被传入第一个参数 args,此参数是一个数组的引用;
  2. main中构造Hello;
  3. main中调用了hello的sayHello,在jvm中实际进行的操作为,向sayHello传入hello作为第一个参数,也就是传入this,注意,this是被传入的~;
  4. 在sayHello中调用了另一个实例方法say,jvm中进行的操作为,向say传入sayHello接收的实参中的第一个参数作为say的this参数;

我们可以得出一个结论,Java语言中,一般(除了奇葩的构造器方法)只有实例方法拥有this,而且,这个this还是作为参数传递进来的。在实例方法中调用静态方法的时候,并没有向静态方法传入this,因此不能在静态方法中调用实例=。=

Objective-C的实现

OC是一个神奇的语言,完全与C兼容,实际上最终的编译也是转化成C的。OC里面等价于this的东西是self。

OC可以动态地添加方法:

#import <objc/runtime.h>
// 中間省略
void myMethodIMP(id self, SEL _cmd) {
    doSomething();
}
class_addMethod([MyClass class], @selector(myMethod), (IMP)myMethodIMP, "v@:");

可以清晰地看到,OC的runtime里面,真正干事儿的方法的C代码表示,接受的参数更加明显,第一个就是self,第二个参数甚至是SEL。

从上可以得知,OC的this也是通过传参实现的。

C++的实现

=。=我并不懂cpp。

但是,从查到的资料看,C++中,所有的class或者struct在编译完成之后,成员函数,都会丢失所有信息,其this指针作为函数首个参数传入。

从上述几个语言看来,this实际上在底层都是作为参数传入的,也就是说js中的call/apply只是把底层暴露出来了而已!!!令人奇怪的是,另外几个语言都不能自定义this,只能由编译器隐式传入。

引申一点,虽然this对于面向对象来说极其重要,但是最终编译的结果中,保留的信息只有成员变量。根本没有什么成员函数,只是一堆能够额外接受一个指针的普通函数。怪不得有人说面向对象是骗局……

renaesop avatar Jun 07 '16 09:06 renaesop

至于为什么在非严格模式下,没有调用者,或者call/apply时传入null时,this会指向window。我觉得可能是因为在全局范围内,this本身就指向window,所以就访问到了外面的this?

renaesop avatar Jun 07 '16 09:06 renaesop

感觉你说的最后一个问题, 貌似并不是问题, 和平常碰到的 this 的情形下一样的吧? 感觉并不是 call/apply 的问题.

xiaoyu2er avatar Oct 26 '16 07:10 xiaoyu2er

@xiaoyu2er 我的标题有问题,其实应该叫“this漫谈”。最后一个问题确实不是什么问题,不过所有的行为应该都是有语义的,我觉得从语义上不能完全理解非严格模式无调用者时this的指向,所以胡诌了一下。

另外,最近认知有所增长,call/apply实现的东西叫“role oriented programming”。

renaesop avatar Oct 26 '16 07:10 renaesop

这个名称取得好, 坐等文章更新

xiaoyu2er avatar Oct 26 '16 08:10 xiaoyu2er

我今天看了一下 <你不知道的 JavaScript 上> 里面讲 this 的时候讲到 在调用 apply/call 第一个参数是 undefined, null 的时候会使用 window, 但是这时候会造成歧义, 因为有可能影响到了 window, 他提出一个 DMZ 的东西... 即 Object.create(null)... 来表达 我希望是个空对象 :D

xiaoyu2er avatar Oct 27 '16 06:10 xiaoyu2er