JavaScript 函数定义
JavaScript 使用关键字 function 定义函数。
函数可以通过声明定义,也可以是一个表达式。
函数声明
在之前的教程中,你已经了解了函数声明的语法 :
执行的代码
}
函数仅仅是声明之后,它是不可能立即执行的,
还要等到 在我们需要的时候 调用它时,它才会真正执行。
实例
return a * b;
}
分号是用来分隔可执行的JavaScript语句。 由于函数声明不是一个可执行语句,所以不以分号结束。 |
函数表达式 匿名函数
JavaScript 函数可以通过一个表达式定义。
函数表达式 是任何一个Javascript可执行语句的组成部分之一
而javascript可执行语句 必须以分号结尾!
函数表达式可以存储在变量中:
实例 注意这是一个可执行语句,必须以分号结尾
当把 函数表达式 存储到变量addFunc后,变量也可作为一个函数使用:
实例
var result = addFunc(4, 3);
以上函数表达式 实际上 是一个 匿名函数 (因为函数没名称,所以必须也只能通过变量来调用)。
函数存储在变量中,不需要函数名称,通常通过变量名来调用。
上述函数表达式(匿名函数)以分号结尾,因为它是一个JavaScript可执行语句的一个组成部分而已。 |
new Function() 构造函数
在以上实例中,我们了解到函数是通过关键字function来进行定义的,
实际上,我们还可以通过关键字new Function()来实现对函数 function 的定义(只是不推荐这样做而已!)。
即使不推荐,但是函数还是可以通过内置的 JavaScript 函数构造器(new Function())进行定义的。
实例
var x = myFunction(6, 7);
实际上,你不必使用构造函数。
上面实例可以写成(函数表达式,即匿名函数,构造JavaScript可执行代码的一部分):
实例
var x = myFunction(4, 3);
在 JavaScript 中,很多时候,你需要避免使用 new 关键字。 |
函数提升(Hoisting)
在之前的教程中我们已经了解了 "hoisting(提升)"。
提升(Hoisting)是 JavaScript 默认将 当前作用域 提升到前面去 的行为。
提升(Hoisting)应用在 变量的声明 与 函数的声明。
因此,JS中的函数 是可以在声明之前就调用的喔~
function myFunction(y) {
return y * 67;
}
千万注意:只有函数声明和变量声明可以hoisting, 而使用表达式定义函数时无法提升(因为匿名,没有函数名,所以即便想提也没法提)。
自调用函数 必须加括号
函数表达式还可以 "自调用"喔。
如果用括号把函数表达式包裹起来之后,再在其后面紧跟 () ,则会自动调用喔~
自调用表达式 会自动调用。
注意:只有函数表达式可以自调用, 声明的函数无法自调用。
通过添加括号,来说明它是一个函数表达式:
实例
var x = "Call Myself!"; // 我将调用自己
})();
以上函数实际上是一个 匿名 自我调用的函数 (没有函数名)。
函数可作为一个值使用
JavaScript 函数作为一个值使用:
实例
return a + b;
}
var result = addFunction(6, 7);
JavaScript 函数可作为表达式使用:
实例 函数返回值 继续参数运算
return a + b;
}
var result = addFunction(4, 3) * 2;
函数是对象
在 JavaScript 中使用 typeof 操作符判断函数类型将返回 "function" 。
但,JavaScript 函数描述为一个对象可能会加准确一点点。
因为 JavaScript 函数 它有自己的 属性 和 方法。
arguments.length 属性返回函数调用过程接收到的参数个数
arguments是函数内置的一个Object类型的属性:
实例
return arguments.length;
}
toString() 方法将函数本身作为一个字符串返回:
实例
return a + b;
}
var result = addFunction.toString();
函数的定义 如果是作为某个对象的属性的话,则该函数被称为该对象的一个方法。 函数如果是被用来创建new出一个新的对象,则该函数被称为该对象的构造函数。 |
JavaScript 函数参数
JavaScript 函数对参数的值(arguments)没有进行任何的检查,真的!啥检查都没有!。
动态弱类型语言JavaScript 的函数参数与大多数其他语言的函数参数的区别在于:
它不会关注参数传递的数量多少,也不会关注传递的参数的数据类型。
函数显式参数 与 隐藏参数(arguments)
在先前的教程中,我们已经学习了函数的显式参数:
code to be executed
}
函数显式参数在函数定义时列出。
函数隐藏参数(arguments)在函数调用时传递给函数真正的值。
参数规则
JavaScript 函数定义时 参数没有指定数据类型。
JavaScript 函数对隐藏参数类型(arguments)没有进行检测。
JavaScript 函数对隐藏参数个数(arguments)没有进行检测。
默认参数 全部是undefined
如果函数在调用时缺少参数,参数会默认设置为: undefined
有时候,这个...还是让人可以接受的,
但是:建议最好为参数设置一个默认值:
实例
if (y === undefined) {
y = 0;
}
}
或者,更简单的方式:
实例
y = y || 0;
}
如果y已经定义 , y || 0返回 y, 因为 y 是 true, 否则y没被传递过来,则返回 0, 因为 undefined 为 false。 |
如果函数调用时传递的实参数目超过函数定义时的形参个数,则多出的实参将无法被引用,因为无法找到对应的参数名。
这个情况下,就只能使用 函数内置的Object类型的arguments属性来调用了。
Arguments 对象
JavaScript 函数 有个内置的 Object类型的对象: arguments 对象.
Object类型的argument 对象,包含了函数调用的参数数组。
通过这种方式你可以很方便的找到最后一个参数的值:
实例
x = findMax(1, 123, 500, 115, 44, 88);
function findMax() {
var i, max = arguments[0];
if(arguments.length < 2){
return max;
}
for (i = 1; i < arguments.length; i++) {
if (arguments[i] > max) {
max = arguments[i];
}
}
return max;
}
或者创建一个函数用来统计所有数值的和:
实例
function sumAll() {
var i, sum = 0;
for (i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum;
}
参数的 值传递 下面一段是绕口令时间
在函数内部调用的参数是函数的 形式参数,通常简称形参。
如果在函数内部,即便修改了形式参数的值,
也并不会影响 传递进来给形式参数的原始变量的值( 即在函数外部定义的那个变量值)。
总结:JavaScript函数传值 只是将参数的值传入函数,
函数会另外分配内存空间 保存这个传递进来的参数值,
所以在函数内部即便是发生了修改,也并不会改变 原来的外部的那个被传递过来的参数的值。
实例
var x = 1;
// 通过值传递参数
function myFunction(x) {
x++; //修改参数x的值,并不会修改在函数外定义的变量 x
console.log(x);
}
myFunction(x); // 2
console.log(x); // 1
通过 对象 传递参数, 传引用
在JavaScript中,可以引用对象的值。
因此我们在函数内部 修改对象的某个属性 就会修改该对象这个属性的初始值。
所以,在函数内部,修改传递进来的对象的属性, 将会影响到 函数外部的那个被传引用的对象的属性值(全局变量)。
实例
var obj = {x:1};
// 通过对象传递参数
function myFunction(obj) {
obj.x++; //修改参数对象obj.x的值,函数外定义的obj也将会被修改
console.log(obj.x);
}
myFunction(obj); // 2
console.log(obj.x); // 2
JavaScript 函数调用
JavaScript 函数有 4 种调用方式。
每种方式的不同在于 this 的初始化。
this 关键字
一般而言,在Javascript中,this指向函数执行时的当前对象。
意思就是,函数执行时,该函数的引用 是属于哪个对象的属性,那么,this就指向了谁
注意 this 是保留关键字,永远也不能修改 this 的值。 |
提示:下面附上 this 关键字单独的一节,为你详细讲解JavaScript this 关键字相关的内容!
在ECMAScript中,this并不限于只用来指向新创建的对象。
让我们更详细的了解一下,在ECMAScript中this到底是什么?
定义
this是执行上下文中的一个属性:
这里VO是我们以后要讨论的变量对象VariableObject。
this与 上下文中 可执行代码的类型 有直接关系,
注意: this值 在进入上下文的那一瞬间 确定,并且 在 上下文 运行期间 永远都不变。
下面让我们更详细研究这些案例:
全局代码中的this
在这里一切都简单。
在全局代码中,this始终是全局对象本身 。
// 显示定义 全局对象global的属性a
this.a = 10; // global.a = 10
alert(a); // 10
// 通过赋值给一个无标示符隐式属性b,没有使用var 就直接赋值,默认就是global的了
b = 20;
alert(this.b); // 20
// 也是通过 变量var声明 隐式声明的,因为省略了默认的this.
// 因为全局上下文 的变量对象 是 全局对象global自身
var c = 30;
alert(this.c); // 30
函数代码中的this
在函数代码中使用this时很有趣,这种情况很难且会导致很多问题。
这种类型的代码中,this值的首要特点(或许是最主要的)是:
this 并不是 静态的 绑定到一个函数。
正如我们上面曾提到的那样,this是在进入了上下文的那一瞬间才确定的,
在一个函数代码中,由于该函数被不同的对象调用,导致了this在每一次都不完全相同(在每一次都不完全相同???)。
不管怎样,在该段函数的代码的运行期间,this值是不会改变的,
也就是说,因为它并不是一个变量,所以不可能为其分配一个新值
(相反,在Python编程语言中,this是明确的定义为对象本身的,所以,在运行期间this可以不断改变)。
var girl = {age: 18};
var loli = {
age: 13,
smile: function () {
alert(this === loli); // true
alert(this.x); // 13
this = girl; // 错误,任何时候不能改变this的值
}
};
// 在进入上下文的那一瞬间
// this被当成loli对象
// determined as "loli" object; why so - will
// be discussed below in detail
loli.smile(); // true, 13
girl.smile = loli.smile;
// this此时,在进入上下文的那一瞬间,this变成了girl
girl.test(); // false, 18
运行效果如下:
那么,影响了函数代码中this值的变化有几个因素:
首先,在通常的函数调用中,this是由激活上下文代码的调用者来提供的,
即调用函数的父上下文(parent context )。
this仅仅取决于 函数的调用方式。
为了在任何情况下准确无误的确定this值,有必要理解和记住这重要的一点。
正是函数的调用方式影响了调用的上下文中的this值,没有别的什么!
即使是正常的全局函数 也会因为 被 不同形式的 调用方式激活(即由于 这些不同的调用方式),才导致了不同的this值。
function foo() {
alert(this);
}
foo(); // global
alert(foo === foo.prototype.constructor); // true
// 但是同一个function的不同的调用方式,将导致this是不同的
foo.prototype.constructor(); // foo.prototype
有可能,比较特殊的情况是:
作为一些对象定义的方法 来调用函数,但是 this却又不会被设置为这个对象。
var foo = {
bar: function () {
alert(this);
alert(this === foo);
}
};
foo.bar(); // foo, true
var exampleFunc = foo.bar;
alert(exampleFunc === foo.bar); // true
// 再一次,同一个function的不同的调用表达式,this是不同的
exampleFunc(); // global, false
那么,调用函数的方式不同,究竟是怎么地 影响和导致this值的不同呢?
为了充分理解this值的确定,需要详细分析其内部类型之一——引用类型(Reference type)。
引用类型(Reference type)
使用伪代码我们可以将引用类型的值可以表示为:
拥有两个属性的对象:
1. base(即拥有属性的那个对象),
2. base中的propertyName(属性名)。
引用类型的 值 只会出现在两种情况时:
- 当我们在处理一个标识符时 (变量名,函数名,函数参数名,属性名)
- 或遇到一个属性访问器 时(如 . 或者 [''] )
标识符的处理过程 在下一篇文章里详细讨论,
在这里我们只需要知道,在该算法的返回值中,总是一个 引用类型的值(这对this来说很重要)。???Excuse Me???
先谈谈第1种情况,标识符时,遇到的引用类型的值
标识符是 变量名,函数名,函数参数名 和 全局对象中 未识别的 属性名。
例如,下面标识符的值:
在上面语句执行的 中间结果 中,会产生一个全新的 引用类型,
这个自动新产生的引用类型对应的值如下:
// 注意:下面的两个都是刚才语句自动产生的 引用类型
var screenWidthReference = {
base: global,
propertyName: 'screenWidth'
};
var showAPPVersionReference = {
base: global,
propertyName: 'showAPPVersion'
};
为了从这个自动产生的中间的全新的 引用类型 中得到一个 对象 它真正的值,
将会执行一个获取对象真正的值的GetValue()方法,
下面用伪代码把GetValue方法做如下描述:
function GetValue(value) { // 如果不是引用类型,直接返回基本类型的值即可 if (Type(value) != Reference) { return value; } // 如果是引用类型,首先获取base对象 var base = GetBase(value); // 如果base对象为null,不存在,抛异常 if (base === null) { throw new ReferenceError; } // ???这个是干嘛???
// 将返回对象属性真正的值(包含对原型链中继承的属性的分析) return base.[Get]); }
内部的[[Get]]方法 返回 对象属性 真正的值,包括对原型链中 继承的属性分析。???Excuse Me???
GetValue(sreenWidthReference);
// 640, GetBase得到base为global对象,
// 然后通过global获取对象的screenWidth属性真正的值
GetValue(showAPPVersionReference);
// 会先GetBase,得到base为global对象,
// 最后,base.[Get]得到对象的showAPPVersion属性真正的值
// ???得到了 function object "showAPPVersion"
// 即showAPPVersion函数
第2种情况是: 属性访问器 都应该熟悉。
它有2种变体:
第1个是: 点(.)语法,如: girl.age 或者girl.show(); (此时 属性名 是 正确的标示符,且事先知道),
第2个是: 括号语法(girl['age']) 或者 girl['show']()
如下图所示:
在中间计算的返回结果中,我们得到了一个全新的自动生成的 引用类型 的值。
该自动生成的 引用类型的值 如下:
var girlShowReference = { base: girl, propertyName: 'show' }; // 然后我们从这个自动生成的中间的引用类型中,
// 获取 对象属性真正的值,即执行: GetValue(girlShowReference);
// 根据上面的分析,我们知道,参数是:引用类型,
// 会先GetBase,得到base为 girl, 最后再base.[Get],
// 得到对象属性真正的值为: function object "show" 函数,
// 最后再执行函数的调用 即()
说了那么多,到底这个 中间自动生成的 引用类型的值
引用类型的值 与 函数上下文中的this值 如何进行关联起来的呢?
——从最重要的意义上来说。 这个关联的过程是这篇文章的核心。
一个函数上下文中 确定它里面的this值 的通用规则如下:
1.在一个函数上下文中,this由调用者提供,
但是,是由调用函数的方式来决定的。
如果调用括号()的左边 是引用类型的值,
那么 this 将被设为 引用类型值的base对象(base object),
2.在其他情况下(与 引用类型 不同的任何其它属性),这个值为null。
不过,实际不存在this的值为null的情况,
因为当this的值为null的时候,其值会被隐式转换为全局对象global。
注:第5版的ECMAScript中,已经不强迫转换成全局变量了,而是赋值为undefined。
我们看看这个例子中的表现:
我们看到在调用括号的左边 即showAPPVersion,
它是一个自动生成的 引用类型值(因为showAPPVersion是一个标示符,是函数名)。
括号的左边,实质上是 下面这个 自动生成的 引用类型的值
这个 引用类型的值,我们在上面专门进行分析过了,
相应地,this 也就设置为 引用类型 的 base对象。即全局对象global了。
同样,使用属性访问器:
在括号的左边, girl.show 它也一个 自动生成的 引用类型的值,该引用类型如下:
var girlShowReference = {
base: girl,
propertyName: 'show'
};
我们再次拥有了一个 引用类型的值,这一次,该引用类型的base是 girl 对象,
所以,在函数show被激活()时,该base对象,被用作为this。
但是,用另外一种形式激活()相同的函数,我们可以 得到 其它的this值。
例如:
因为函数的左边是testFunc 作为标识符,这样,就自动生成了一个新的 引用类型的值,
其base(全局对象)将用作this 值。如下所示:
现在,我们可以很明确的告诉你,为什么用表达式的不同形式 激活()同一个函数会不同的this值,
答案在于中间 自动生成的 不同的 引用类型(type Reference)的值。
function showAPPVersion() {
alert(this);
}
showAPPVersion(); // 弹出 global, because
var showAPPVersionReference = {
base: global,
propertyName: 'showAPPVersion'
};
// 下面函数虽然是同一个,=== 为true,但是
alert(showAPPVersion === showAPPVersion.prototype.constructor); // true
// 但是,它是以另外一种形式在调用表达式
showAPPVersion.prototype.constructor(); // 弹出 showAPPVersion.prototype, because
var showAPPVersionPrototypeConstructorReference = {
base: showAPPVersion.prototype,
propertyName: 'constructor'
}; // 这儿的base 是 showAPPVersion.protype了,它将作为函数的上下文的this
另外一个通过调用方式动态确定this值的经典例子:
function showGirl() {
alert(this.name + "'s age is " + this.age);
}
var menma = {name:"面码",age: 15};
var mathilda = {name:"mathilda", age: 12};
menma.show = showGirl;
mathilda.show = showGirl;
menma.show(); // 面码's age is 15
mathilda.show(); // mathilda's age is 12
函数调用 和 非引用类型
因此,正如我们已经指出,当调用 括号的左边 不是 引用类型 而是其它类型,这个值自动设置为null,结果为全局对象global。
注意,前面说过,引用类型只有两种情况下出现,1.标识符,2.属性获取器(点 或 中括号)
让我们再思考这种表达式:
括号的左边,是一个匿名函数, 它不是引用类型(因为没有标识符,也不是某个对象的属性)
在这个例子中,我们有一个函数对象但不是引用类型的对象(它不是标示符,也不是属性访问器),
因为,函数内的this值先被设置为null,最终隐式地 设为全局对象。
更多复杂的例子:
var foo = {
bar: function () {
alert(this);
}
};
foo.bar(); // Reference, OK => foo
(foo.bar)(); // Reference, OK => foo ???Excuse Me???
(foo.bar = foo.bar)(); // global?
(false || foo.bar)(); // global?
(foo.bar, foo.bar)(); // global?
为什么我们有一个属性访问器,它的中间值应该为引用类型的值,在某些调用中我们得到的this值不是base对象,而是global对象?
问题在于后面的三个调用,在应用一定的运算操作之后,在调用括号的左边的值不在是引用类型。
- 第一个例子很明显———明显的括号左边是属性访问器, 会自动生成的中间的 引用类型,因此,this为 引用类型的base对象,即foo。
- 在第二个例子中,组运算符并不适用,想想上面提到的,从 引用类型 中获得一个对象真正的值的方法,如GetValue。相应的,在组运算的返回中———我们得到仍是一个 引用类型 ???Why???。这就是this值为什么再次设为base对象,即foo。
- 第三个例子中,与组运算符不同,赋值运算符中已经调用过了GetValue方法。此时,返回的结果是已经对象真正的函数对象了(但并不是引用类型),这意味着this设为null,结果是global对象。
- 第四个和第五个也是一样——逗号运算符和逻辑运算符(OR)中,虽然自动产生了中间的引用类型,但是引用类型已经调用了GetValue 方法,得到了引用类型真正的值,即函数对象, 因为,相应地,我们失去了引用类型 而得到了 真正的函数对象。只要括号左边不是引用类型,那么this就是null,因此null并再次隐式地转为了global。
引用类型 和 this为null
还有一种特殊情况是这样的:
当调用表达式 限定了 call括号左边的 引用类型的值 时,
这个时候,this会被设定为null,???Excuse Me???
但结果 this 又会被隐式转化成global。
尤其是:当 引用类型值 的base对象 是一个 活动对象???时,这种情况就会出现。
正如我们在第12章知道的一样,局部变量、内部函数、形式参数 全部是储存在给定函数的激活对象中。
下面的实例中,内部函数 被 父函数调用,此时我们就能够看到上面说的那种特殊情况。
function foo() { function bar() { alert(this); // global }
// 这儿会自动生成一个 活动对象ActiveObject
// 直接调用内部函数bar();就相当于调用一个活动对象ActiveObject.bar();
// 而 活动对象,总是作为this返回,且值为null
// 因此,下面一句代码相当于null.bar();
// 因为this就是null,所以this被隐式转成了全局对象global bar(); // the same as ActiveObject.bar() }
活动对象 总是作为this返回,且值为null (即伪代码的ActiveObject.bar()相当于null.bar())。
因为this 是null,所以这里我们再次回到上面描述的例子,this 被隐式设置为全局对象global。
如图所示:
另外,还有一种情况除外:
如果with对象 包含有一个函数名的属性,并且在with语句的内部块中调用了该函数。
With对象 将添加到该 对象作用域 的最前端???,即在活动对象ActiveObject的前面。
相应地,也就有了自动生成的中间的 引用类型(通过 标示符 或 属性访问器),
这个引用类型的base对象 不再是 活动对象(null),而是with语句的对象 WithObejct。
顺便提一句,它不仅与内部函数相关,也与全局函数相关,
因为 with对象 比 作用域链里的最前端的对象(全局对象 或 一个活动对象) 还要 靠前。
var x = 10;
with ({
foo: function () {
alert(this.x);
},
x: 20
}) {
foo(); // 20
}
// because
var fooReference = {
base: __withObject,
propertyName: 'foo'
};
同样的情况出现在 catch语句的实际参数是 函数, 且被调用时:
在这种情况下,catch对象 被添加到作用域的 最前端,即在 活动对象 或 全局对象 的前面。
但是,这个特定的行为被确认为ECMA-262-3的一个bug,这个在新版的ECMA-262-5中修复了。
这样,在特定的活动对象中,this指向全局对象。而不是catch对象。
try {
throw function () {
alert(this);
};
} catch (e) {
e(); // ES3标准里是catchObject, ES5标准里是global
}
// on idea
var eReference = {
base: catchObject,
propertyName: 'e'
};
// ES5新标准里已经fix了这个bug,
// 所以this就是全局对象了
var eReference = {
base: global,
propertyName: 'e'
};
同样的情况出现在 命名函数(函数的更对细节参考第15章Functions)的 递归调用 中。
在函数的第一次调用中,base对象是父活动对象(或全局对象),
但是在 递归调用 中,base对象 应该是 存储着函数表达式 可选名称的 特定对象。
但是,在这种情况下,this总是指向全局对象。
(function foo(bar) {
alert(this);
!bar && foo(1); // "should" be special object, but always (correct) global
})(); // global
作为构造器 调用的函数中的 this
还有一个与this值相关的情况是:
在函数的上下文中,这是一个构造函数的调用。
function Girl() {
alert(this); // "loli"对象下创建一个新属性
this.age = 13;
}
var loli = new Girl();
alert(loli.age); // 13
在这个例子中,new运算符 调用“Girl”函数的内部的[[Construct]] 方法,
接着,在对象创建后,调用内部的[[Call]] 方法。
所有相同的函数“Girl” 都将this的值设置为新创建的对象。
函数调用中 手动设置this (call和apply)
在函数原型中定义的两个神奇的方法允许去手动设置函数调用的this值。
因此所有函数都可以访问它,它们就是.call和.apply方法。当当当当~
他们把 接受到的第1个参数 作为this值,
这个 this 将在 函数调用的作用域中使用。
这两个方法的区别很小很少喔,
两个方法 必须传的参数是第1个—— 传递进来用来成为 this 的变量。
对于.apply,第二个参数必须是数组(或者是类似数组的Object对象,如arguments)
而.call 则可以接受任何参数。
例如:
var b = 10;
function a(c) {
alert(this.b);
alert(c);
}
a(20); // this === global, this.b == 10, c == 20
a.call({b: 20}, 30); // this === {b: 20}, this.b == 20, c == 30
a.apply({b: 30}, [40]) // this === {b: 30}, this.b == 30, c == 40
结论
在这篇文章中,我们讨论了ECMAScript中this关键字的特征(对比于C++ 和 Java,它们的确是特色)。
希望大家能够准确的理解ECMAScript中this关键字如何工作。
调用 JavaScript 函数
在之前的章节中我们已经学会了如何创建函数。
函数中的代码 是在 函数被调用后 才执行的。
第1种.作为一个函数调用
实例
return a + b;
}
add(6, 7); // add(6, 7) 返回 13
以上函数不属于任何对象。
但是,在 JavaScript 中它又始终是 默认的全局对象。
并且,在 HTML 中默认的全局对象是 HTML 页面本身,
所以,函数 是属于 HTML 页面的。
由于,在浏览器中的页面对象 是 浏览器窗口(window 对象)。
因此,以上函数会自动变为 window 对象的函数。
add() 和 window.add() 是一样的:
实例
return a + b;
}
window.add(6, 7); // window.add(6, 7) 返回 13
这是调用 JavaScript 函数常用的方法, 但不是良好的编程习惯 全局变量,方法或函数容易造成命名冲突的bug。 |
全局对象
当函数没有被自身的对象调用时, this 的值 就会变成全局对象。
在 web 浏览器中全局对象 是 浏览器窗口(window 对象)。
该实例返回 this 的值是 window 对象:
实例
return this;
}
show(); // 返回 window 对象,这儿会自动生成中间的 引用类型 ,具体推倒过程,请看上面this一节
函数作为 全局对象 调用,会使 this 的值成为 全局对象 。 使用 window 对象 作为一个变量 容易造成程序崩溃。 |
第2种.函数作为方法调用
在 JavaScript 中你可以将函数定义为 对象的方法。
以下实例创建了一个对象 (myObject),
对象有两个属性 (firstName 和 lastName),
及一个方法 (fullName):
实例 也会涉及到自动生成的引用类型,推倒过程,请参阅上面的this一节
name: "面码",
age: 13,
showGirl: function () {
return this.name + "'s age is " + this.age + '岁';
}
}
girl.showGirl(); // 返回 "面码's age is 13岁"
showGirl 方法是一个函数。
函数属于对象。
girl 是函数的所有者。
this对象,拥有 JavaScript 代码。
实例中 this 的值为 girl 对象。
下面测试一下!修改 showGirl 方法并返回 this 值:
实例
name:"面码",
age: 13,
showGirl: function () {
return this;
}
}
girl.showGirl(); // 返回 [object Object] (所有者对象)
函数作为对象方法调用,会使得 this 的值 成为 对象本身。 |
第3种:使用构造函数调用函数
如果函数调用前使用了 new 关键字, 则是调用了 构造函数。
这表面上看起来就像创建了新的函数,
但实际上 JavaScript 函数 是 重新创建的对象:
实例
function myFunction(arg1, arg2) {
this.firstName = arg1;
this.lastName = arg2;
}
// This creates a new object
var x = new myFunction("John","Doe");
x.firstName; // 返回 "John"
构造函数的调用会 创建一个新的对象。
这个 新对象 会 继承 构造函数的 属性和方法。
构造函数中 this 关键字没有任何的值。 this 的值 是在函数被调用的那一瞬间, 实例化一个新对象(new object)时赋值的。 |
作为函数方法 调用函数
在 JavaScript 中, 函数 也是 对象。
JavaScript 函数 有它自己的 属性和方法。
call() 和 apply() 是函数内部 预定义的方法。
因此,每一个函数 都自带call()和apply()方法
两个方法可用于调用函数,
注意: 两个方法的第1个参数 必须是 对象本身。
实例
return a + b;
}
myFunction.call(myObject, 6, 7); // 返回 13
实例
return a + b;
}
myArray = [6,7];
myFunction.apply(myObject, myArray); // 返回 13
两个方法都使用了 对象本身 作为第1个参数。
两者的区别在于第二个参数:
apply传入的是一个参数数组,也就是将多个参数组合成为一个数组传入,
而call则作为call的参数传入(从第2个参数开始)。
在 JavaScript 严格模式(strict mode)下, 在调用函数时第1个参数会成为 this 的值, 无论该参数是不是一个对象。
在 JavaScript 非严格模式(non-strict mode)下, 如果第1个参数的值是 null 或 undefined, 它将使用全局对象进行替代。
通过 call() 或 apply() 方法你可以设置 this 的值, 且作为已存在对象的新方法调用。 |
JavaScript 闭包
JavaScript 变量 可以是 局部变量 或 全局变量。
私有变量 可以用到 闭包。
全局变量
函数可以访问函数内部定义的变量 (局部变量),如:
实例
var a = 5;
return a * a;
}
函数也可以访问 函数外部 定义的变量 (全局变量),如:
实例
function myFunction() {
return a * a;
}
后面一个实例中, a 是一个 全局变量。
在web页面中 全局变量 属于 window 对象。
全局变量 可应用于页面上的所有脚本。
在第1个实例中, a 是一个 局部 变量。
局部变量 只能用于定义它函数内部。
对于其他的函数 或 脚本代码 是不可用的。
全局和局部变量即便名称相同,它们也是两个不同的变量。修改其中一个,不会影响另一个的值。
变量声明是如果不使用 var 关键字,那么它默认就是一个全局变量,即便它在函数内定义。 |
变量的 生命周期
全局变量 的作用域是全局性的,即在整个JavaScript程序中,全局变量 时时处处都在。
而在函数内部声明的变量,只在函数内部起作用。
这些变量是局部变量,作用域是局部性的;
函数的参数也是局部性的,所以也只在函数内部起作用。
计数器困境
设想下如果你想统计一些数值,且该计数器在所有函数中都是可用的。
你可以使用全局变量,函数设置计数器递增:
实例
function add() {
counter += 1;
}
add();
add();
add();
// 计数器现在为 3
计数器数值 在每次执行 add() 函数时发生+1的变化 。
如下图所示:
但问题来了,页面上的任何脚本都能直接去 改变计数器count的值,即使在没有调用 add() 函数的情况下。
如果我在在函数内声明的计数器,那么,如果没有调用函数,将无法修改计数器的值:
实例
var counter = 0;
counter += 1;
}
add();
add();
add();
// 本意是想输出 3, 但事与愿违,输出的都是 1 !
以上代码将无法正确输出,因为每次我调用 add() 函数,计数器都会设置为 1。
下面,我们隆重介绍 JavaScript的 内嵌函数,它就可以完美解决该问题。
JavaScript 内嵌函数
所有函数 都能访问 全局变量。
实际上,在 JavaScript 中,所有函数 都能访问 它们上一层的作用域。
JavaScript 支持 嵌套函数。
嵌套函数 可以访问 上一层的函数变量。
该实例中,内嵌函数 innerAdd() 可以访问 父函数的 counter 变量:
实例
var counter = 0;
function innerAdd() {counter += 1;}
innerAdd();
return counter;
}
如果我们能在外部访问 innerAdd() 函数,这样就能解决计数器的困境。
我们同样需要确保 counter = 0 只执行一次。
我们需要再次隆重介绍 闭包。
JavaScript 闭包
还记得 函数自我调用 吗?该函数会做什么?
实例
var counter = 0;
return function () {return counter += 1;}
})();
add();
add();
add();
// 计数器为 3
实例解析
变量 add 指定了函数自我调用后的返回值 (返回的是一个子函数)。
自我调用函数只执行一次。即设置了计数器count为 0。并返回子函数表达式。
虽然父函数已经不在了,但是留给子函数一个宝贵的count,
因为父函数的count 被子函数引用了,所以即使父函数已经不在了,count还是依然驻留内存
add变量 可以作为一个函数使用。 (因为父函数自我调用我,返回的是一个子函数)
非常棒的地方就是, 因为子函数持有父函数的计数器count,
所以,子函数是可以访问函数上一层父函数(虽然已经不在了)作用域的计数器count。
这个就叫作 JavaScript 闭包。
它使得子函数 拥有了 外部其他函数都无法访问 的私有变量(即 从 已经不在了的父函数 那儿持有的变量count) 变成可能!
计数器count受 匿名子函数的作用域保护,因此只能通过 add 方法进行访问和修改。
外部其他任何函数都没有权力 干涉!
闭包 是可访问上一层父函数作用域里 变量 的函数,即便上一层函数已经关闭,不存在了。 |
相关文章参考
JavaScript学习笔记:学习Javascript闭包
未完待续,下一章节,つづく