1.call/apply/bind 方法的来源
首先, 在使用 call,apply,bind 方法时, 我们有必要知道这三个方法究竟是来自哪里? 为什么可以使用的到这三个方法?
call,apply,bind 这三个方法其实都是继承自 Function.prototype 中的, 属于实例方法.
- console.log(Function.prototype.hasOwnProperty('call')) //true
- console.log(Function.prototype.hasOwnProperty('apply')) //true
- console.log(Function.prototype.hasOwnProperty('bind')) //true
上面代码中, 都返回了 true, 表明三种方法都是继承自 Function.prototype 的. 当然, 普通的对象, 函数, 数组都继承了 Function.prototype 对象中的三个方法, 所以这三个方法都可以在对象, 数组, 函数中使用.
关于继承的概念, 会在以后与大家分享.
2.Function.prototype.call()
函数实例的 call 方法, 可以指定该函数内部 this 的指向(即函数执行时所在的作用域), 然后在所指定的作用域中, 调用该函数. 并且会立即执行该函数.
看个例子来好好理解这段话.
- var keith = {
- rascal: 123
- };
- var rascal = 456;
- function a() {
- console.log(this.rascal);
- }
- a(); //456
- a.call(); //456
- a.call(null); //456
- a.call(undefined); //456
- a.call(this); //456
- a.call(keith); //123
上面代码中, a 函数中的 this 关键字, 如果指向全局对象, 返回结果为 456. 可以看到, 如果 call 方法没有参数, 或者参数为 null 或 undefined 或者 this, 则等同于指向全局对象. 如果使用 call 方法将 this 关键字指向 keith 对象, 也就是将该函数执行时所在的作用域为 keith 对象, 返回结果为 123.
call()方法可以传递两个参数. 第一个参数是指定函数内部中 this 的指向(也就是函数执行时所在的作用域), 第二个参数是函数调用时需要传递的参数.
- function keith(a, b) {
- console.log(a + b);
- }
- keith.call(null, 1, 2); //3
第一个参数是必须的, 可以是 null,undefined,this, 但是不能为空. 设置为 null,undefined,this 表明函数 keith 此时处于全局作用域. 第二个参数中必须一个个添加. 而在 apply 中必须以数组的形式添加.
call 方法的一个应用是调用对象的原生方法. 也可以用于将类数组对象转换为数组.
- var obj = {};
- console.log(obj.hasOwnProperty('toString')); //false
- obj.hasOwnProperty = function() {
- return true;
- }
- console.log(obj.hasOwnProperty('toString')); //true
- console.log(Object.prototype.hasOwnProperty.call(obj, 'toString')); //false
上面代码中, hasOwnProperty 是 obj 对象继承的方法, 如果这个方法一旦被覆盖, 就不会得到正确结果. call 方法可以解决这个方法, 它将 hasOwnProperty 方法的原始定义放到 obj 对象上执行, 这样无论 obj 上有没有同名方法, 都不会影响结果. 要注意的是, hasOwnProperty 是 Object.prototype 原生对象的方法, 而 call 是继承自 Function.prototype 的方法.
3.Function.prototype.apply()
apply 方法的作用与 call 方法类似, 也是改变 this 指向(函数执行时所在的作用域), 然后在指定的作用域中, 调用该函数. 同时也会立即执行该函数. 唯一的区别就是, 它接收一个数组作为函数执行时的参数.
apply 方法的第一个参数也是 this 所要指向的那个对象, 如果设为 null 或 undefined 或者 this, 则等同于指定全局对象. 第二个参数则是一个数组, 该数组的所有成员依次作为参数, 在调用时传入原函数. 原函数的参数, 在 call 方法中必须一个个添加, 但是在 apply 方法中, 必须以数组形式添加.
看一下 call,apply 的细微差别.
- function keith(a, b) {
- console.log(a + b);
- }
- keith.call(null, 2, 3); //5
- keith.apply(null, [2, 3]); //5
上面代码中, 第一个参数为 null, 指向全局作用域; 第二个参数传入的形式稍稍不同.
apply 方法有以下应用.
3.1: 找出数组中的最大数
- var a = [2, 4, 5, 7, 8, 10];
- console.log(Math.max.apply(null, a)); //10
- console.log(Math.max.call(null,2, 4, 5, 7, 8, 10)); //10
Javascript 中是没有提供找出数组中最大值的方法的, 结合使用继承自 Function.prototype 的 apply 和 Math.max 方法, 就可以返回数组的最大值.
3.2: 将数组的空元素变为 undefined
通过 apply 方法, 利用 Array 构造函数将数组的空元素变成 undefined.
1 console.log(Array.apply(null, [1, , 3])); // [1, undefined, 3]
空元素与 undefined 的差别在于, 数组的 forEach 方法会跳过空元素, 但是不会跳过 undefined 和 null. 因此, 遍历内部元素的时候, 会得到不同的结果.
- var a = [1, , 3];
- a.forEach(function(index) {
- console.log(index); //1,3 , 跳过了空元素.
- })
- Array.apply(null,a).forEach(function(index){
- console.log(index); ////1,undefined,3 , 将空元素设置为 undefined
- })
3.3: 转换类似数组的对象
另外, 利用数组对象的 slice 方法, 可以将一个类似数组的对象 (比如 arguments 对象) 转为真正的数组. 当然, slice 方法的一个重要应用, 就是将类似数组的对象转为真正的数组. call 和 apply 都可以实现该应用.
- console.log(Array.prototype.slice.apply({0:1,length:1})); //[1]
- console.log(Array.prototype.slice.call({0:1,length:1})); //[1]
- console.log(Array.prototype.slice.apply({0:1,length:2})); //[1,undefined]
- console.log(Array.prototype.slice.call({0:1,length:2})); //[1,undefined]
- function keith(a,b,c){
- return arguments;
- }
- console.log(Array.prototype.slice.call(keith(2,3,4))); //[2,3,4]
上面代码的 call,apply 方法的参数都是对象, 但是返回结果都是数组, 这就起到了将对象转成数组的目的. 从上面代码可以看到, 这个方法起作用的前提是, 被处理的对象必须有 length 属性, 以及相对应的数字键.
4.Function.prototype.bind()
bind 方法用于指定函数内部的 this 指向(执行时所在的作用域), 然后返回一个新函数. bind 方法并非立即执行一个函数.
- var keith = {
- a: 1,
- count: function() {
- console.log(this.a++);
- }
- };
- keith.count(); //1
- keith.count(); //2
- keith.count(); //3
上面代码中, 如果 this.a 指向 keith 对象内部的 a 属性, 如果这个方法赋值给另外一个变量, 调用时就会出错.
- var keith = {
- a: 1,
- count: function() {
- console.log(this.a++);
- }
- };
- var f = keith.count;
- f(); //NaN
上面代码中, 如果把 count 方法赋值给 f 变量, 那么 this 对象指向不再是 keith 对象了, 而是 window 对象. 而 window.a 默认为 undefined, 进行递增运算之后 undefined++ 就等于 NaN.
为了解决这个问题, 可以使用 bind 方法, 将 keith 对象里的 this 绑定到 keith 对象上, 或者是直接调用.
- var f = keith.count.bind(keith);
- f(); //1
- f(); //2
- f(); //3
- keith.count.bind(keith)() //1
- keith.count.bind(keith)() //2
- keith.count.bind(keith)() //3
当然, this 也可以绑定到其他对象上.
- var obj = {
- a: 100
- };
- var f = keith.count.bind(obj);
- f(); //100
- f(); //101
- f(); //102
同样, 我们也可以给 bind 方法传递参数, 第一个参数如果为 null 或者 undefined 或者 this, 会将函数内部的 this 对象指向全局环境; 第二个为调用时需要的参数, 并且传递参数的形式与 call 方法相同.
- function keith(a, b) {
- return a + b;
- }
- console.log(keith.apply(null,[1,4])); //5
- console.log(keith.call(null,1,4)); //5
- console.log(keith.bind(null, 1, 4)); //keith()
- console.log(keith.bind(null, 1, 4)()); //5
上面代码中, 可以看出 call,apply,bind 三者的区别: call 和 apply 方法都是在调用之后立即执行的. 而 bind 调用之后是返回原函数, 需要再调用一次才行, 有点像闭包的味道, 如果对闭包概念不熟悉, 可以浏览这两篇文章: javascript-- 函数参数与闭包 -- 详解 http://www.cnblogs.com/Uncle-Keith/p/5792485.html ,javascript 中重要概念 - 闭包 - 深入理解 http://www.cnblogs.com/Uncle-Keith/p/5801015.html .
5. 绑定回调函数的对象
在这篇文章 javascript 之 this 关键字详解 http://www.cnblogs.com/Uncle-Keith/p/5814578.html 中, 有谈及到如果在回掉函数中使用 this 对象, 那么 this 对象是会指向 DOM 对象, 也就是 button 对象. 如果要解决回调函数中 this 指向问题, 可以用如下方法.
- var o = {
- f: function() {
- console.log(this === o);
- }
- }
- $('#button').on('click', function() {
- o.f.apply(o);
- // 或者 o.f.call(o);
- // 或者 o.f.bind(o)();
- });
点击按钮以后, 控制台将会显示 true. 由于 apply 方法 (或者 call 方法) 不仅绑定函数执行时所在的对象, 还会立即执行函数(而 bind 方法不会立即执行, 注意区别), 因此不得不把绑定语句写在一个函数体内.
6.call,apply,bind 方法的联系和区别
其实用于指定函数内部的 this 指向的问题, 这三个方法都差不多, 只是存在形式上的差别. 读者可以将以上的例子用三种方法尝试用三种方法实现.
总结一下 call,apply,bind 方法:
a: 第一个参数都是指定函数内部中 this 的指向(函数执行时所在的作用域), 然后根据指定的作用域, 调用该函数.
b: 都可以在函数调用时传递参数. call,bind 方法需要直接传入, 而 apply 方法需要以数组的形式传入.
c:call,apply 方法是在调用之后立即执行函数, 而 bind 方法没有立即执行, 需要将函数再执行一遍. 有点闭包的味道.
d: 改变 this 对象的指向问题不仅有 call,apply,bind 方法, 也可以使用 that 变量来固定 this 的指向.
来源: http://www.bubuko.com/infodetail-2753797.html