JavaScript函数(arguments,this)的理解

句安
• 阅读 3751

javaScript因为其语法松散,导致函数(尤其是this)看似简单,其实里面花头很多。本篇介绍一下JavaScript函数及其调用方法。
• 函数声明和函数表达式
• arguments
• this
• this补充说明

函数声明和函数表达式
JavaScript里对象字面量产生的对象将被连接到Object.prototype,函数对象将被连接到Function.prototype(但该对象本身也连接到Object.prototype)。先看一下函数声明和函数表达式(分匿名和命名):

function count(a,b){ return a*b; }              //函数声明
var d1 = function(n) { return n*2; };           //匿名函数表达式
var d2 = function double(n) { return n*2; };    //命名函数表达式

console.log(count(3,4));    //12
console.log(d1(3));         //6
console.log(d2(3));         //6
console.log(double(3));    //error,double未定义

上面代码可以看出函数声明和函数表达式在后续的调用中,效果是没有差别的。除语法不同外,两者的区别在于JS解析器读取的顺序。

解析器会事先读取函数声明,即使你把函数声明放在代码的末端也没关系。而对于函数表达式,同其它基本类型的变量一样,只有在执行到该行语句时才解析。因此用函数表达式时,必须确保它在调用语句之前,否则会报错。
再看匿名和命名函数表达式的区别。上例中命名函数表达式将函数绑定到变量d2上,而非变量double上,因此double(3);会出现未定义error。

那命名函数表达式有什么用呢?比如上面的变量double有什么用呢?函数名double可用于在函数内部做递归,但可惜仍旧没必要,因为变量d2同样也可以在函数内部递归。因此命名函数表达式真正的作用在于调试,JavaScript环境提供对Error对象的栈追踪功能,可以用double进行栈追踪。

但命名函数表达式仍旧有很多问题,类似with一样。因此通常推荐用匿名函数表达式,不推荐用命名函数表达式:

var d1 = function(n) { return n*2; };           //Yes,推荐
var d2 = function double(n) { return n*2; };    //No,不推荐

arguments
每个函数都接受2个附加参数:this和arguments。先看arguments。JS的函数参数其实就是个类似数组的arguments对象,是对形参的一个映射,但是值是通过索引来获取的。因此JS的函数天然支持可变参数。
arguments对象看似像数组,但请不要使用arguments.shift()等方法来修改arguments。修改arguments对象将可能导致命名参数失去意义。

例如person(name, age),参数name是arguments[0]的别名,age是arguments[1]的别名,如果用shift移除arguments后,name仍旧是arguments[0]的别名,age仍旧是arguments[1]的别名,函数开始失控。
因此,如果你无论如何要修改arguments,需要先将arguments对象转化为真正的数组:
var args = [].slice.call(arguments);

之后对args对象进行shift()等操作。这也常见于获取可变参数值,同样需要上述那样将arguments对象转化为真正的数组。
另外每个arguments对象都有两个额外的属性:arguments.callee和arguments.caller。前者指向使用该arguments对象被调用的函数。后者指向调用该arguments对象的函数。

其实arguments.callee除允许匿名函数递归调用自身外,并没有什么太大用处。但可惜用函数名也能实现递归,所以它真没什么用处:

//用arguments.callee来递归
var factorial = (function(n) {
    return (n <= 1) ? 1 : (n * arguments.callee(n - 1));    //递归
});

//但也可以直接用函数名来递归
function factorial(n) {

return (n <= 1) ? 1 : (n * factorial(n - 1));    

}
用arguments.caller可以跟踪栈信息,但它不可靠,如果某函数在栈中出现了不止一次,很容易陷入死循环,大多数环境已经移除了此特性。
JS严格模式下禁止使用arguments.callee和arguments.caller,因此这两个属性就不多废话了。
this
arguments介绍完后,再来看看this。在JS中this取决于调用的方式,不同的函数调用方式,this绑定的对象也不同。有4种调用方式:
• 方法调用
• 函数调用
• 构造器调用
• apply / call / bind调用
方法调用:当函数作为对象方法时,函数里的this被绑定到该var myNum = {

value: 0,
increment: function(inc) {  //函数作为对象方法
    this.value += inc;
}

};
myNum.increment(2);
console.log(myNum.value); //2,this被绑定到myNum用:函数非对象方法时,this被绑定到全局对象window。这其实是语言设计上的一个错误(或曰特性),导致this不能调用内部函数。要调用内部函数,可以将that = this保存起来。
function double(n){ return n*2; } //普通函数,this绑定到全局对象window

//错误的例子
myNum.count = function() {

var helper = function() {
    this.value = double(this.value);
};
helper();

}
myNum.count();
console.log(myNum.value); //value不变

//正确的例子:
myNum.count = function() {

var that = this;       
var helper = function() {
    that.value = double(that.value);   //现在参数是myNum.value
};
helper();

}
myNum.count();
console.log(myNum.value); //4

错误的例子中,期望this绑定的是对象myNum,但由于double是普通函数,因此this绑定的是window,而window显然没有value。即helper里this是window,因此double(this.value);不会被执行。最终myNum的value值并没有变。
正确的例子在对象myNum方法里,this绑定的是myNum对象,因此先用that将this保存起来。然后在内部传递的都是that,回避了helper函数内this发生改变的问题。这里写代码片
构造函数调用:用new调用构造函数,会先创建一个连接到构造函数的prototype的新对象,再将this会绑定到该新对象

var Name = function(n) { 
    this.name = n; 
}
Name.prototype.getName = function() {
    return this.name;
}
var myName = new Name("Jack");      //this绑定到myName对象
console.log(myName.getName());          //Jack
apply / call / bind调用:允许我们自己绑定想要的this
var friend = {
    name: "Betty"
};
console.log(Name.prototype.getName.apply(friend));    //Betty
console.log(Name.prototype.getName.call(friend));     //Betty
console.log(Name.prototype.getName.bind(friend)());   //Betty

this补充说明
这一节并无任何新的内容,只不过对this进一步补充说明一下。我们知道对象都有prototype俗称原型对象。那prototype里的this绑定谁呢?其实原则没有变,从上面构造函数调用的例子就能看出this仍旧是绑定调用的对象。
为了更清晰一点,将上面构造函数调用的例子稍微改一下:

var Name = function() {};
Name.prototype =  {
    name: "(not set)",
    setName: function(n) {
        this.name = n;
    }
}

var myName = new Name();
console.log(myName.name);                   //(not set)
console.log(myName.hasOwnProperty("name"));    //false
console.log(myName.hasOwnProperty("setName"));   //false

myName.setName("Jack");
console.log(myName.name);                       //Jack
console.log(myName.hasOwnProperty("name"));    //true
console.log(myName.hasOwnProperty("setName"));   //false

先看第一段结果代码,Name本身没有任何属性,name和setName是在它的原型prototype中定义的。因此用hasOwnProperty来检查全是false。这与我们的预想完全一致,没什么可奇怪的。
再看第二段结果代码,由于执行了myName.setName("Jack");。原型prototype中的this不是绑定原型对象,而是绑定调用的对象。即setName中的this绑定的是对象myName,会给对象增加一个name属性。所以hasOwnProperty("name")会为true。

self补充说明

这个非常简单。我们知道,打开任何一个网页,浏览器会首先创建一个窗口,这个窗口就是一个window对象,也是js运行所依附的全局环境对象和全局作用域对象。self 指窗口本身,它返回的对象跟window对象是一模一样的。也正因为如此,window对象的常用方法和函数都可以用self代替window。举个例子,常见的写法如“self.close();”,把它放在标记中:“关闭窗口”,单击“关闭窗口”链接,当前页面关闭。

明白这些原理后,再回过头看看以前不明白的代码里this,that,self等就轻松多了。

更多资源上:去转盘;或者加我的QQ群参与js,css的讨论学习(QQ群:512245829)

点赞
收藏
评论区
推荐文章
Karen110 Karen110
3年前
一篇文章带你了解JavaScript作用域
在JavaScript中,对象和函数也是变量。在JavaScript中,作用域是你可以访问的变量、对象和函数的集合。JavaScript有函数作用域:这个作用域在函数内变化。一、本地JavaScript变量一个变量声明在JavaScript函数内部,成为函数的局部变量。局部变量有局部作用域:它们只能在函数中访问。JS://codeherecann
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
小嫌 小嫌
3年前
Javascript中的变量提升
定义JavaScript中奇怪的一点是你可以在变量和函数声明之前使用它们。就好像是变量声明和函数声明被提升了代码的顶部一样。sayHi()//Hithere!functionsayHi()console.log('Hithere!')name'JohnDoe'console.log(name)//JohnDoevarn
Jacquelyn38 Jacquelyn38
4年前
重学JavaScript第1集|变量提升
变量提升就好比JavaScript引擎用一个很小的代码起重机将所有var声明和function函数声明都举起到所属作用域(所谓作用域,指的是可访问变量和函数的区域)的最高处。这句话的意思是:如果在函数体外定义函数或使用var声明变量。则变量和函数的作用域会提升到整个代码的最高处,此时任何地方访问这个变量和调用这个函数都不会报错;而在函数体内定义函数或使用va
Stella981 Stella981
3年前
Android入门:HTML布局中Android程序与JAVASCRIPT的交互
1、JAVASCRIPT调用Android里面的方法//主函数publicclassVMusicActivityextendsActivity{@OverridepublicvoidonCreate(BundlesavedInstanceState){
Stella981 Stella981
3年前
JavaScript之函数
    玩js自然要和函数打交到。函数嘛简单来说就是给代码分个块,方便调用、信息隐藏和代码复用,还可以用于指定对象的行为。另外函数还可以玩出很多花样来。。。JavaScript使用关键字function定义函数。定义一个函数://函数声明//这种定义函数的好处是可以在当前作用域内任何位置调用,因为变量的声明和函数的
Stella981 Stella981
3年前
HIVE 时间操作函数
日期函数UNIX时间戳转日期函数: from\_unixtime语法:   from\_unixtime(bigint unixtime\, string format\)返回值: string说明: 转化UNIX时间戳(从19700101 00:00:00 UTC到指定时间的秒数)到当前时区的时间格式举例:hive   selec
Wesley13 Wesley13
3年前
JS原型、原型链深入理解
原型是JavaScript中一个比较难理解的概念,原型相关的属性也比较多,对象有”prototype”属性,函数对象有”prototype”属性,原型对象有”constructor”属性。原型是JavaScript中一个比较难理解的概念,原型相关的属性也比较多,对象有”prototype”属性,函数对象有”prototype”属性,原型对
Stella981 Stella981
3年前
JavaScript学习总结(3)——JavaScript函数(function)
一、函数基本概念  为完成某一功能的程序指令(语句)的集合,称为函数。二、JavaScript函数的分类  1、自定义函数(我们自己编写的函数),如:functionfunName(){}  2、系统函数(JavaScript自带的函数),如alert函数。三、函数的调用方式
Stella981 Stella981
3年前
JavaScript(js)字面量,函数写法
JavaScript字面量在编程语言中,一般固定值称为字面量,如3.14。数字(Number)字面量可以是整数或者是小数,或者是科学计数(e)。3.141001123e5字符串(String)字面量可以使用单引号或双引号:"JohnDoe"'JohnDoe'表达式字面量用于计算:
Stella981 Stella981
3年前
JavaScript基础2
普通的JavaScript对象是命名值的无序集合,JavaScript同样定义了一种特殊的对象数组array,表示带编号的值的有序集合,JavaScript为数组定义了专用的语法,使得数组具有区别于普通对象而独有的行为特性JavaScript还定义了另一种特殊对象函数,函数是具有与它相关联的可执行代码的对象,通过调用函数来运行可执行代码并返回运算结