Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

call/apply漫谈 #7

Open
renaesop opened this issue Jun 7, 2016 · 5 comments
Open

call/apply漫谈 #7

renaesop opened this issue Jun 7, 2016 · 5 comments

Comments

@renaesop
Copy link
Owner

renaesop commented Jun 7, 2016

在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
Copy link
Owner Author

renaesop commented Jun 7, 2016

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

@xiaoyu2er
Copy link

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

@renaesop
Copy link
Owner Author

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

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

@xiaoyu2er
Copy link

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

@xiaoyu2er
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants