对于 Javascript 中的 apply 和 call 方法定义中的一个对象替换另一个对象不明白

2014-06-01 09:02:50 +08:00
 ety001
先看代码:
https://gist.github.com/ety001/8c0d19ae0405b01d589d

以下是某篇博文对于call和apply的定义:
call方法:
语法:call([thisObj[,arg1[, arg2[, [,.argN]]]]])
定义:调用一个对象的一个方法,以另一个对象替换当前对象。
说明:
call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。
如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。

apply方法:
语法:apply([thisObj[,argArray]])
定义:应用某一对象的一个方法,用另一个对象替换当前对象。
说明:
如果 argArray 不是一个有效的数组或者不是 arguments 对象,那么将导致一个 TypeError。
如果没有提供 argArray 和 thisObj 任何一个参数,那么 Global 对象将被用作 thisObj, 并且无法被传递


[不懂的地方]
以另一个对象替换当前对象。
如果是替换的话,为何我写的代码中的20行打印出来的this会有name,age,school和grade成员?因为第一个apply会把当前function的this替换掉,第二个apply又把当前的this替换掉了。
我看网上很多文章都说的是替换。是我的代码验证的不严谨吗?

[我的理解]
Obj.apply(thisObj,[])
如果把apply和call分别翻译成“应用”和“调用”,那么我的理解是,thisObj应用(调用)Obj,并把[]作为Obj的传入参数,而不是以[]作为Obj的参数并替换thisObj。
3727 次点击
所在节点    问与答
16 条回复
hitsmaxft
2014-06-01 09:47:27 +08:00
call 和 apply 只是传参方式不同, 作用都一样的, 执行一个函数,并替换作用域里的 this 指向的实例

这段代码里面带 this 的 函数, 可以当成构造器, 那么用了 apply 相当于把当前的 this 重新初始化了一遍, 所以属性修改了.

你的不理解跟 apply 的工作原理没啥关系, 是不熟悉 javascript 的构造器写法导致的..
ety001
2014-06-01 11:06:24 +08:00
@hitsmaxft 貌似你没有看懂我要表达的问题。我的问题其实是质疑用“替换”这个词来表述,因为我写的那个代码的执行结果并不是“替换”,而是“补充”,如果是“替换”的话,第20行打印出来的this应该是Print的内容才对,而实际打印的内容是Person,Print,Student2的合并后的内容。

因为看到很多人都是用“替换”这个词,所以我在想是不是我哪个地方没有理解正确。

就像你所说的,使用apply相当于是把当前的this重新初始化了一遍,问题就出在这个重新初始化,到底应该是表述为用一个类去替换当前类,还是用一个类去补充当前类。实际的操作来看,这是一个补充的过程。
SoloCompany
2014-06-01 11:09:08 +08:00
把 this 看成一个显示参数而不是隐式参数,那么 call 和 apply 的左右就容易理解了,这两个方法用途是完全一样的,使用哪个取决于你的参数是怎么取得,call 和你直接调用一个方法完全一样,只是第一个参数变成了 this,其余参数位置自动加一,apply 就完全不同,所有参数作为数组放到第二个参数中传递
ispinfx
2014-06-01 11:42:59 +08:00
输出的东西有你说合并的东西没啥问题啊,我觉得是LZ没弄清楚被替换的是当前对象这里.
Mutoo
2014-06-01 11:45:50 +08:00
> [我的理解]
> Obj.apply(thisObj,[])

很明显,你这里误解了一个东西, Obj. 应该是 Function. 才对

Obj是构造函数,或者一般化为普通函数。

函数执行时有上下文(context),js通过这个上下文来查找变量。

function.apply(thisObj, [arguments..]) 的意思是
以 thisObj 作为 this 的引用变量,执行 function(arguments...)
ispinfx
2014-06-01 11:51:05 +08:00
作为JS小白,我表示我是这样理解的.

https://gist.github.com/isolet/1dc64eb6e9be3bfe1842
serenader
2014-06-01 12:10:45 +08:00
在执行 var s = new Student2('Jim',22,'Harvard',2); 这个表达式时其实 Student2 函数内部的 this 指向的都是新实例,也就是 s 。

而所谓的替换过程其实是发生在调用 apply 或者 call 的这个函数内部的。在你这个例子中,

var Student2 = function(name,age,school,grade){
Person.apply(this,arguments);
Print.apply(this,arguments);
console.log(this);
this.school = school;
this.grade = grade;
}

Person.apply(this,arguments) 的意思是,调用 Person 这个函数,然后将 Person 内部的 this 替换为(或者说切换为)apply 方法传递进去的第一个参数,也就是 this 。

不知道你理解不。

“Obj.apply(thisObj,[])
如果把apply和call分别翻译成“应用”和“调用”,那么我的理解是,thisObj应用(调用)Obj,并把[]作为Obj的传入参数,而不是以[]作为Obj的参数并替换thisObj。”

理解有些不当。应该是, 调用 Obj 这个函数,并且将 Obj 的上下文切换为传递进 apply 方法的第一个参数。然后 [] 是作为 Obj 的参数传递的。而这整个过程中,Student2 的 this 并没有变。当构造新实例时是始终指向新实例的。

所以你给出的例子就可以很好的理解了。

附上 jsfiddle 。我只是写了一些注释。 http://jsfiddle.net/Jx8r7/

另外楼主可以看看这两个链接,有帮助你理解 this 和 call/apply

阮一峰:Javascript的this用法

http://www.ruanyifeng.com/blog/2010/04/using_this_keyword_in_javascript.html

郭培:Javascript中call的使用

http://hszy00232.blog.163.com/blog/static/43022753201131835653841/
emric
2014-06-01 12:39:20 +08:00
lijsh
2014-06-01 12:44:39 +08:00
楼主,console的一行是不会有school和grade属性输出的,因为还没到这两个属性的赋值部分。

替换这个说法是没错的,对于apply和call,第一个参数就是代表上下文的替换(当然切换更好)。

这里当以new调用Student2的时候,Student2里的this都会指向实例后的对象,这里就是s;既然内部的this都是指向同一个引用,两个apply所谓的替换看起来更像补充作用。
lijsh
2014-06-01 12:47:34 +08:00
楼主的第二条补充有点不严谨,应该是在Student2里把Person的上下文切换到Student2中的this。
zzNucker
2014-06-01 12:53:24 +08:00
我觉得lz的补充说明他还是不懂。。。。。
andy12530
2014-06-01 13:04:26 +08:00
我是不能理解为毛20行不能 打印出name,age,school和grade,都说是替换了,又不是把Student2里面的this删掉,后面又对this添加了school等属性。

顺便说一句,浏览器里面的console.log是异步的。
andy12530
2014-06-01 13:13:15 +08:00
『其实apply就是把你想要进行继承操作的构造方法(比如我例子中的Student2)的上下文交给你想要集成的构造方法』

LZ 你还是没理解。

apply和call方法的功能,只是 "调用一个函数", 把这个 函数里面的 this 变成你传递的第一个参数。

例子:Array.prototype.slice.call(ListCollection),本来slice这个方法只能在array上调用,通过call方法,我们可以slice方法在非数组元素上。
ety001
2014-06-01 15:06:43 +08:00
@lijsh
以18行的代码为例,就是把当前Student2的this传递给了Person,apply会把Person作为实施目标,用传入的Student2的this替换原有的this,所谓的替换,应该就是这一步,之前理解不了,就是因为把这个替换的方向搞反了,一直认为是Person的this替换了Student2的this。
说白了,就是把Student2的this告诉了Person,让Person可以直接操作这个this,通过这个方式让Student2拥有Person的一些属性。


@andy12530 浏览器里面的console.log是异步的,这个之前还真是不清楚。不过上午做这个试验的时候,看到这个结果,有想到这个console.log可能会是异步。另外我觉得你说的“apply和call方法的功能,只是 "调用一个函数", 把这个 函数里面的 this 变成你传递的第一个参数”跟我理解的意思是一致的。
jakwings
2014-06-01 16:17:23 +08:00
相信大家已经解释清楚了。我补一个相关的细节问题:经过 .bind(this) 产生的新函数,无法再替换 this,即是说:
function a() { console.log(this); }
a.call({x: 1}); //=> {x: 1}
b = a.bind({y: 2});
b.call({x: 1}); //=> {y: 2}
lijsh
2014-06-01 18:15:06 +08:00
@andy12530 老版的webkit中确实是这样,现在基本不会了,我在新版chrome中测了,console没表现出异步,没输出后面赋值的属性。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/115267

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX