如果没接触过动态语言,以编译型语言的思维方式去理解javaScript将会有种神奇而怪异的感觉,因为意识上往往不可能的事偏偏就发生了,甚至觉得不可理喻.如果在学JavaScript这自由而变幻无穷的语言过程中遇到这种感觉,那么就从现在形始,请放下的您的”偏见”,因为这对您来说绝对是一片新大陆。

区分apply,call就一句话,

foo.call(this, arg1,arg2,arg3) == foo.apply(this, arguments)==this.foo(arg1, arg2, arg3)

call, apply都属于Function.prototype的一个方法,它是JavaScript引擎内在实现的,因为属于Function.prototype,所以每个Function对象实例,也就是每个方法都有call, apply属性.既然作为方法的属性,那它们的使用就当然是针对方法的了.这两个方法是容易混淆的,因为它们的作用一样,只是使用方式不同.

相同点:两个方法产生的作用是完全一样的

不同点:方法传递的参数不同

那什么是方法产生的作用,方法传递的参数是什么呢?

我们就上面的foo.call(this, arg1, arg2, arg3)展开分析.

foo是一个方法,this是方法执行时上下文相关对象,arg1, arg2, arg3是传给foo方法的参数.这里所谓的方法执行时上下文相关对象, 如果有面向对象的编程基础,那很好理解,就是在类实例化后对象中的this.

在JavaScript中,代码总是有一个上下文对象,代码处理该对象之内. 上下文对象是通过this变量来体现的, 这个this变量永远指向当前代码所处的对象中.

//创建一个A类
function A(){
//类实例化时将运行以下代码
//此时的执行上下文对象就是this,就是当前实例对象
this.message = "a的消息";
this.getMessage = function(){
return this.message;
}
} //创建一个B类
function B(){
this.message = "b的消息";
this.setMessage = function(msg){
this.message = msg;
}
} //A, B类都有一个message属性(面向对象中所说的成员),A有获取消息的getMessage方法,B有设置消息的setMessage方法 //创建一个B类实例对象
var b = new B(); //给对象a动态指派b的setMessage方法,注意,a本身是没有这方法的!
b.setMessage.call(a, "a的消息"); //创建一个A类实例对象
var a = new A(); //下面将显示"a的消息"
//给对象b动态指派a的getMessage方法,注意,b本身也是没有这方法的!
alert(a.getMessage());

call, apply作用就是借用别人的方法来调用,就像调用自己的一样.

function print(a, b, c, d){
alert(a + b + c + d);
}
function example(a, b , c , d){
//用call方式借用print,参数显式打散传递
print.call(this, a, b, c, d);
//用apply方式借用print, 参数作为一个数组传递,
//这里直接用JavaScript方法内本身有的arguments数组
print.apply(this, arguments);
//或者封装成数组
print.apply(this, [a, b, c, d]);
}
//下面将显示”背光脚本”
example(”背” , “光” , “脚”, “本”);

call, apply方法区别是,从第二个参数起, call方法参数将依次传递给借用的方法作参数, 而apply直接将这些参数放到一个数组中再传递, 最后借用方法的参数列表是一样的.

当参数明确时可用call, 当参数不明确时可用apply给合arguments

print.call(window, “背” , “光” , “脚”, “本”);
//foo参数可能为多个
function foo(){
print.apply(window, arguments);
}

下面我们来理解一下this,然后再巩固一下call和apply

<script type="text/javascript">
this.a = "aaa";
console.log(a);
//aaa console.log(this.a);
//aaa console.log(window.a);
//aaa console.log(this);
// window console.log(window);
// window console.log(this == window);
// true console.log(this === window);
// true
</script>

this指针就是window对象,我们看到即使使用三等号它们也是相等的。

全局作用域常常会干扰我们很好的理解javascript语言的特性,这种干扰的本质就是:

在javascript语言里全局作用域可以理解为window对象,记住window是对象而不是类,也就是说window是被实例化的对象,这个实例化的过程是在页面加载时候由javascript引擎完成的,整个页面里的要素都被浓缩到这个window对象,因为程序员无法通过编程语言来控制和操作这个实例化过程,所以开发时候我们就没有构建这个this指针的感觉,常常会忽视它,这就是干扰我们在代码里理解this指针指向window的情形。

干扰的本质还和function的使用有关,我们看看下面的代码:

<script type="text/javascript">
function ftn01(){
console.log("I am ftn01!");
}
var ftn02 = function(){
console.log("I am ftn02!");
}
</script>

第一种定义函数的方式在javascript语言称作声明函数,第二种定义函数的方式叫做函数表达式

这两种方式我们通常认为是等价的,但是它们其实是有区别的,而这个区别常常会让我们混淆this指针的使用,我们再看看下面的代码:

<script type="text/javascript">
console.log(ftn01);
//ftn01() console.log(ftn02);
// undefined function ftn01(){
console.log("I am ftn01!");
}
var ftn02 = function(){
console.log("I am ftn02!");
}
</script>

在javascript语言通过声明函数方式定义函数,javascript引擎在预处理过程里就把函数定义和赋值操作都完成了,在这里我补充下javascript里预处理的特性,其实预处理是和执行环境相关,在上篇文章里我讲到执行环境有两大类:全局执行环境和局部执行环境,执行环境是通过上下文变量体现的,其实这个过程都是在函数执行前完成,预处理就是构造执行环境的另一个说法,总而言之预处理和构造执行环境的主要目的就是明确变量定义,分清变量的边界,但是在全局作用域构造或者说全局变量预处理时候对于声明函数有些不同,声明函数会将变量定义和赋值操作同时完成,因此我们看到上面代码的运行结果。由于声明函数都会在全局作用域构造时候完成,因此声明函数都是window对象的属性,这就说明为什么我们不管在哪里声明函数,声明函数最终都是属于window对象的原因了

关于函数表达式的写法还有秘密可以探寻,我们看下面的代码:

<script type="text/javascript">
function ftn03(){
var ftn04 = function(){
console.log(this);
// window
};
ftn04();
}
ftn03();
</script>

运行结果我们发现ftn04虽然在ftn03作用域下,但是执行它里面的this指针也是指向window,其实函数表达式的写法我们大多数更喜欢在函数内部写,因为声明函数里的this指向window这已经不是秘密,但是函数表达式的this指针指向window却是常常被我们所忽视,特别是当它被写在另一个函数内部时候更加如此。

其实在javascript语言里任何匿名函数都是属于window对象,它们也都是在全局作用域构造时候完成定义和赋值,但是匿名函数是没有名字的函数变量,但是在定义匿名函数时候它会返回自己的内存地址,如果此时有个变量接收了这个内存地址,那么匿名函数就能在程序里被使用了,因为匿名函数也是在全局执行环境构造时候定义和赋值,所以匿名函数的this指向也是window对象,所以上面代码执行时候ftn04的this也是指向window,因为javascript变量名称不管在那个作用域有效,堆区的存储的函数都是在全局执行环境时候就被固定下来了,变量的名字只是一个指代而已。

这下子坏了,this都指向window,那我们到底怎么才能改变它了?

在本文开头我说出了this的秘密,this都是指向实例化对象,前面讲到那么多情况this都指向window,就是因为这些时候只做了一次实例化操作,而这个实例化都是在实例化window对象,所以this都是指向window。我们要把this从window变成别的对象,就得要让function被实例化,那如何让javascript的function实例化呢?答案就是使用new操作符。我们看看下面的代码:

<script type="text/javascript">
var obj = {
name:"Larry",
job:"Web",
show:function(){
console.log("Name:" + this.name + ";Job:" + this.job);
console.log(this);
// Object { name="Larry", job="Web", show=function()}
}
}; var otherObj = new Object(); otherObj.name = "Devin";
otherObj.job = "Bank";
otherObj.show = function(){
console.log("Name:" + this.name + ";Job:" + this.job);
console.log(this);
// Object { name="Devin", job="Bank", show=function()}
}; obj.show();
//Name:Larry;Job:Web otherObj.show();
//Name:Devin;Job:Bank
</script>

这里的this指针不是指向window的,而是指向Object的实例,firebug的显示让很多人疑惑,其实Object就是面向对象的类,大括号里就是实例对象了,即obj和otherObj。Javascript里通过字面量方式定义对象的方式是new Object的简写,二者是等价的,目的是为了减少代码的书写量,可见即使不用new操作字面量定义法本质也是new操作符,所以通过new改变this指针的确是不过攻破的真理。

new操作符会让构造函数产生如下变化:

1.       创建一个新对象;

2.       将构造函数的作用域赋给新对象(因此this就指向了这个新对象);

3.       执行构造函数中的代码(为这个新对象添加属性);

4.       返回新对象

Javascript还有一种方式可以改变this指针,这就是call方法和apply方法,call和apply方法的作用相同,就是参数不同,call和apply的第一个参数都是一样的,但是后面参数不同,apply第二个参数是个数组,call从第二个参数开始后面有许多参数。Call和apply的作用是什么,这个很重要,重点描述如下:

Call和apply是改变函数的作用域(有些书里叫做改变函数的上下文)

Call和apply是将this指针指向方法的第一个参数。

我们看看下面的代码:

<script type="text/javascript">
var name = "Larry";
function ftn(name){
console.log(name);
console.log(this.name);
console.log(this);
}
ftn("100");
var obj = {
name:"Devin"
};
ftn.call(obj,"200"); /* *
结果如下所示: 100 Larry Window 200 Devin Object * */
</script>

情形一:传入的参数是函数的别名,那么函数的this就是指向window

情形二:传入的参数是被new过的构造函数,那么this就是指向实例化的对象本身;

情形三:如果我们想把被传入的函数对象里this的指针指向外部字面量定义的对象,那么我们就是用apply和call

this new call() apply()的更多相关文章

  1. JS核心系列:浅谈 call apply 与 bind

    在JavaScript 中,call.apply 和 bind 是 Function 对象自带的三个方法,这三个方法的主要作用是改变函数中的 this 指向,从而可以达到`接花移木`的效果.本文将对这 ...

  2. SQL Server-聚焦APPLY运算符(二十七)

    前言 其实有些新的特性在SQL Server早就已经出现过,但是若非系统的去学习数据库你会发现在实际项目中别人的SQL其实是比较复杂的,其实利用新的SQL Server语法会更加方便和简洁,从本节开始 ...

  3. 利用apply()或者rest参数来实现用数组传递函数参数

    关于call()和apply()的用法,MDN文档里写的非常清晰明白,在这里就不多做记录了. https://developer.mozilla.org/zh-CN/docs/Web/JavaScri ...

  4. 由js apply与call方法想到的js数据类型(原始类型和引用类型)

    原文地址:由js apply与call方法想到的js数据类型(原始类型和引用类型) js的call方法与apply方法的区别在于第二个参数的不同,他们都有2个参数,第一个为对象(即需要用对象a继承b, ...

  5. JavaScript学习笔记(二)——闭包、IIFE、apply、函数与对象

    一.闭包(Closure) 1.1.闭包相关的问题 请在页面中放10个div,每个div中放入字母a-j,当点击每一个div时显示索引号,如第1个div显示0,第10个显示9:方法:找到所有的div, ...

  6. 瞬间记住Javascript中apply与call的区别

    关于Javascript函数的apply与call方法的用法,网上的文章很多,我就不多话了.apply和call的作用很相似,但使用方式有区别 apply与call的第一个参数都是一个对象,这个对象就 ...

  7. scope.$apply是干嘛的

    开始用angular做项目的时候,一定碰到过$scope.$apply()方法,表面上看,这像是一个帮助你进行数据更新的方法,那么,它为何存在,我们又该如何使用它呢. JavaScript执行顺序 J ...

  8. JavaScript中的apply,call与this的纠缠

    1.apply定义 apply:调用函数,并用指定对象替换函数的 this 值,同时用指定数组替换函数的参数. 语法:apply([thisObj[,argArray]]) thisObj 可选.要用 ...

  9. jQuery之常用且重要方法梳理(siblings,nextAll,end,wrap,apply,call,each)-(二)

    1.siblings() siblings() 获得匹配集合中每个元素的同胞,通过选择器进行筛选是可选的. <body> <div><span>Hello</ ...

  10. JS中 call() 与apply 方法

    1.方法定义 call方法: 语法:call([thisObj[,arg1[, arg2[,   [,.argN]]]]]) 定义:调用一个对象的一个方法,以另一个对象替换当前对象. 说明: call ...

随机推荐

  1. 团队Alpha冲刺(四)

    目录 组员情况 组员1(组长):胡绪佩 组员2:胡青元 组员3:庄卉 组员4:家灿 组员5:凯琳 组员6:丹丹 组员7:何家伟 组员8:政演 组员9:鸿杰 组员10:刘一好 组员:何宇恒 展示组内最新 ...

  2. 关《我是IT小小鸟》有感

    我一直认为大学就是一个自由的舒适的学习环境,没有人可以干扰你限制你,以至于我到了大学之后只剩下了颓废的生活.每天上课玩手机,下课玩电脑,吃饭叫外卖,从不去锻炼,周末就熬夜通宵,状态越来越差,导致我逐渐 ...

  3. 第14章 Linux账号管理与ACL权限设置

    Linux的账号与用户组 用户标识符:UID与GID 每一个文件都有一个所有者ID和用户组ID,当我们需要查看文件属性时,系统会根据/etc/passwd和/etc/group的内容,找到对应UID和 ...

  4. 转 从红帽、GitHub和Docker看开源商业模式的进阶

    从红帽.GitHub和Docker看开源商业模式的进阶 发表于2014-12-16 10:26| 7594次阅读| 来源http://stratechery.com/| 0 条评论| 作者Ben Th ...

  5. Node.js系列——(1)安装配置与基本使用

    1.安装 进入下载地址 小编下载的是msi文件,下一步下一步傻瓜式安装. 打印个hello看看: 2.REPL 全称Read Eval Print Loop,即交互式解释器,可以执行读取.执行.打印. ...

  6. ADO之command

    connection      command对象使用的数据库连接 commandText     执行的SQL语句 ExecuteNonQuery  执行不返回行的语句,如UPDATE等 Execu ...

  7. WPF中Image控件的Source属性的设置

    1.直接关联到文件,关联后不能删除此图片,因为图片正在使用. imageEditImage.Source = new BitmapImage(new Uri(strImagePath, UriKind ...

  8. 【一】shiro入门 之 Shiro简介

    Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE 环境,也可以用在JavaEE 环境.Shiro 可以帮助我们完成:认证.授权.加密.会话管理.与Web 集成.缓存等.这不就是 ...

  9. [十三]SpringBoot 之 过滤器(Filter)和监听器(Listener)

    过滤器(Filter)和 监听器(Listener)的注册方法和 Servlet 一样,不清楚的可以查看下上一篇文章 代码示例 package me.shijunjie.filter; import ...

  10. 【BZOJ3672】【NOI2014】购票(线段树,斜率优化,动态规划)

    [BZOJ3672][NOI2014]购票(线段树,斜率优化,动态规划) 题解 首先考虑\(dp\)的方程,设\(f[i]\)表示\(i\)的最优值 很明显的转移\(f[i]=min(f[j]+(de ...