JS_04_函数表达式_自调用函数_this_内嵌函数_闭包



JavaScript 函数定义


JavaScript 使用关键字 function 定义函数。

函数可以通过声明定义,也可以是一个表达式


函数声明

在之前的教程中,你已经了解了函数声明的语法 :

function functionName( parameters) {
  执行的代码
}

函数仅仅是声明之后,它是不可能立即执行的,

还要等到 在我们需要的时候 调用它时,它才会真正执行。

实例

function myFunction(a, b) { 
    return a * b; 

Note分号是用来分隔可执行的JavaScript语句。 
由于函数声明不是一个可执行语句,所以不以分号结束。

函数表达式  匿名函数

JavaScript 函数可以通过一个表达式定义。

函数表达式  是任何一个Javascript可执行语句的组成部分之一

javascript可执行语句 必须以分号结尾!

函数表达式可以存储在变量中:

实例    注意这是一个可执行语句,必须以分号结尾

var addFunc = function (a, b) {return a + b};


当把 函数表达式 存储到变量addFunc后,变量也可作为一个函数使用:

实例

var addFunc = function (a, b) {return a + b}; 
var result = addFunc(4, 3);


以上函数表达式 实际上 是一个 匿名函数 (因为函数没名称,所以必须也只能通过变量来调用)。

函数存储在变量中,不需要函数名称,通常通过变量名来调用。

Note上述函数表达式(匿名函数)以分号结尾,因为它是一个JavaScript可执行语句的一个组成部分而已。


new Function() 构造函数

在以上实例中,我们了解到函数是通过关键字function来进行定义的,

实际上,我们还可以通过关键字new Function()来实现对函数 function 的定义(只是不推荐这样做而已!)。

即使不推荐,但是函数还是可以通过内置的 JavaScript 函数构造器(new Function())进行定义的。

实例

var myFunction = new Function("a", "b", "return a * b"); 

var x = myFunction(6, 7);


实际上,你不必使用构造函数。

上面实例可以写成(函数表达式,即匿名函数,构造JavaScript可执行代码的一部分):

实例

var myFunction = function (a, b) {return a * b} ;

var x = myFunction(4, 3);
Note在 JavaScript 中,很多时候,你需要避免使用 new 关键字


函数提升(Hoisting)

在之前的教程中我们已经了解了 "hoisting(提升)"。

提升(Hoisting)是 JavaScript 默认将  当前作用域  提升到前面去  的行为。

提升(Hoisting)应用在  变量的声明  与  函数的声明

因此,JS中的函数  是可以在声明之前就调用的喔~ 

myFunction(520);
function myFunction(y) {
    return y * 67;
}

千万注意:只有函数声明和变量声明可以hoisting, 而使用表达式定义函数时无法提升(因为匿名,没有函数名,所以即便想提也没法提)。


自调用函数  必须加括号

函数表达式还可以 "自调用"喔。

如果用括号把函数表达式包裹起来之后,再在其后面紧跟 () ,则会自动调用喔~

自调用表达式    会自动调用。


注意:只有函数表达式可以自调用,  声明的函数无法自调用。

通过添加括号,来说明它是一个函数表达式:

实例

(function () { 
    var x = "Call Myself!";      // 我将调用自己 
})();

以上函数实际上是一个 匿名  自我调用的函数 (没有函数名)。



函数可作为一个值使用

JavaScript 函数作为一个值使用:

实例

function addFunction(a, b) { 
    return a + b; 


var result = addFunction(6, 7);

JavaScript 函数可作为表达式使用:

实例  函数返回值 继续参数运算

function addFunction(a, b) { 
    return a + b; 


var result = addFunction(4, 3) * 2;


函数是对象

在 JavaScript 中使用 typeof 操作符判断函数类型将返回 "function" 。

但,JavaScript 函数描述为一个对象可能会加准确一点点。

因为 JavaScript 函数 它有自己的 属性 和 方法

arguments.length 属性返回函数调用过程接收到的参数个数

arguments是函数内置的一个Object类型的属性:


实例

function myFunction(a, b) { 
    return arguments.length; 
}

toString() 方法将函数本身作为一个字符串返回:


实例

function addFunction(a + b) { 
    return a + b; 


var result = addFunction.toString();
Note函数的定义 如果是作为某个对象的属性的话,则该函数被称为该对象的一个方法。 
函数如果是被用来创建new出一个新的对象,则该函数被称为该对象的构造函数。



JavaScript 函数参数


JavaScript 函数对参数的值(arguments)没有进行任何的检查,真的!啥检查都没有!。

动态弱类型语言JavaScript函数参数与大多数其他语言的函数参数的区别在于:

它不会关注参数传递的数量多少,也不会关注传递的参数的数据类型。


函数显式参数  与  隐藏参数(arguments)

在先前的教程中,我们已经学习了函数的显式参数:

functionName( parameter1, parameter2, parameter3) {
     code to be executed
}

函数显式参数在函数定义时列出。

函数隐藏参数(arguments)在函数调用时传递给函数真正的值。


参数规则

JavaScript 函数定义时 参数没有指定数据类型。

JavaScript 函数对隐藏参数类型(arguments)没有进行检测。

JavaScript 函数对隐藏参数个数(arguments)没有进行检测。


默认参数 全部是undefined

如果函数在调用时缺少参数,参数会默认设置为: undefined

有时候,这个...还是让人可以接受的,

但是:建议最好为参数设置一个默认值

实例

function myFunction(x, y) {
    if (y === undefined) {
          y = 0;
    } 
}


或者,更简单的方式:

实例  

function myFunction(x, y) {
    y = y || 0;
}
Note如果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;

}



或者创建一个函数用来统计所有数值的和:

实例

x = sumAll(1, 123, 500, 115, 44, 88);

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就指向了谁

Note注意 this 是保留关键字,永远也不能修改 this 的值

提示:下面附上 this 关键字单独的一节,为你详细讲解JavaScript this 关键字相关的内容!


在ECMAScript中,this并不限于只用来指向新创建的对象。

让我们更详细的了解一下,在ECMAScript中this到底是什么?

定义

this是执行上下文中的一个属性:

activeExecutionContext = {
  VO: {...},
  this: thisValue
};

这里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(属性名)。

var valueOfReferenceType = {
  base: <base object>,
  propertyName: <property name>
};

引用类型的   只会出现在两种情况时:

  1.     当我们在处理一个标识符时 (变量名,函数名,函数参数名,属性名)
  2.     或遇到一个属性访问器 (如 . 或者 [''] )


标识符的处理过程 在下一篇文章里详细讨论,

在这里我们只需要知道,在该算法的返回值中,总是一个 引用类型的值(这对this来说很重要)。???Excuse Me???


先谈谈第1种情况,标识符时,遇到的引用类型的值

标识符是 变量名,函数名,函数参数名 和 全局对象中 未识别的 属性名

例如,下面标识符的值:

var screenWidth = 640;
function showAPPVersion() {}

在上面语句执行的 中间结果 中,会产生一个全新的 引用类型,

这个自动新产生的引用类型对应的如下: 

// 注意:下面的两个都是刚才语句自动产生的 引用类型

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']() 

如下图所示:


girl.show();
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。


我们看看这个例子中的表现:

function showAPPVersion() {
  return this;
}

showAPPVersion(); // global

我们看到在调用括号的左边 即showAPPVersion,

它是一个自动生成的 引用类型值(因为showAPPVersion是一个标示符,是函数名)。

括号的左边,实质上是 下面这个  自动生成的 引用类型的值 

var showAPPVersionReference = {
  base: global,
  propertyName: 'showAPPVersion'
};

这个 引用类型的值,我们在上面专门进行分析过了,
相应地,this 也就设置为
引用类型 的 base对象。即全局对象global了。


同样,使用属性访问器:

var girl = {
  show: function () {
    return this;
  }
};

girl.show(); // girl

在括号的左边, girl.show 它也一个 自动生成的 引用类型的值,该引用类型如下:

var girlShowReference = {

    base: girl,

    propertyName: 'show'

};

我们再次拥有了一个 引用类型的值,这一次,该引用类型的base是 girl 对象,

所以,在函数show被激活()时,该base对象,被用作为this。


var girlShowReference = {
  base: girl,
  propertyName: 'show'
};

但是,用另外一种形式激活()相同的函数,我们可以 得到 其它的this值。

例如:

var testFunc = girl.show;
testFunc(); // global

因为函数的左边是testFunc 作为标识符,这样,就自动生成了一个新的 引用类型的值,

其base(全局对象)将用作this 值。如下所示:

var testFuncReference = {
  base: global,
  propertyName: 'testFunc'
};

现在,我们可以很明确的告诉你,为什么用表达式的不同形式 激活()同一个函数会不同的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.属性获取器(点 或 中括号)

让我们再思考这种表达式:

括号的左边,是一个匿名函数, 它不是引用类型(因为没有标识符,也不是某个对象的属性)

(function () {
  alert(this); // null => global
})();

在这个例子中,我们有一个函数对象但不是引用类型的对象(它不是标示符,也不是属性访问器),

因为,函数内的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对象?

问题在于后面的三个调用,在应用一定的运算操作之后,在调用括号的左边的值不在是引用类型。

  1. 第一个例子很明显———明显的括号左边是属性访问器, 会自动生成的中间的 引用类型,因此,this为 引用类型的base对象,即foo。
  2. 在第二个例子中,组运算符并不适用,想想上面提到的,从 引用类型 中获得一个对象真正的值的方法,如GetValue。相应的,在组运算的返回中———我们得到仍是一个 引用类型 ???Why???。这就是this值为什么再次设为base对象,即foo。
  3. 第三个例子中,与组运算符不同,赋值运算符中已经调用过了GetValue方法。此时,返回的结果是已经对象真正的函数对象了(但并不是引用类型),这意味着this设为null,结果是global对象。
  4. 第四个和第五个也是一样——逗号运算符和逻辑运算符(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种.作为一个函数调用

实例

function add(a, b) {
    return a + b;
}
add(6, 7);           // add(6, 7) 返回 13

以上函数不属于任何对象。

但是,在 JavaScript 中它又始终是 默认的全局对象

并且,在 HTML 中默认的全局对象是 HTML 页面本身,

所以,函数 是属于 HTML 页面的。

由于,在浏览器中的页面对象 浏览器窗口(window 对象)

因此,以上函数会自动变为 window 对象的函数。

add() 和 window.add() 是一样的:

实例

function add(a, b) {
    return a + b;
}
window.add(6, 7);    // window.add(6, 7) 返回 13
Note这是调用 JavaScript 函数常用的方法, 但不是良好的编程习惯 
全局变量,方法或函数容易造成命名冲突的bug。


全局对象

当函数没有被自身的对象调用时, this 的值 就会变成全局对象。

在 web 浏览器中全局对象 是 浏览器窗口(window 对象)。

该实例返回 this 的值是 window 对象:


实例

function show() {
    return this;
}
show();                // 返回 window 对象,这儿会自动生成中间的 引用类型 ,具体推倒过程,请看上面this一节
Note函数作为 全局对象 调用,会使 this 的值成为 全局对象 。
使用 window 对象 作为一个变量 容易造成程序崩溃

第2种.函数作为方法调用

在 JavaScript 中你可以将函数定义为 对象的方法

以下实例创建了一个对象 (myObject), 

对象有两个属性 (firstName 和 lastName), 

及一个方法 (fullName):

实例  也会涉及到自动生成的引用类型,推倒过程,请参阅上面的this一节

var girl = {
    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 值:

实例

var girl = {
    name:"面码",
    age: 13,
    showGirl: function () {
        return this;
    }
}
girl.showGirl();          // 返回 [object Object] (所有者对象)
Note函数作为对象方法调用,会使得  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"

构造函数的调用会 创建一个新的对象

这个 新对象 会 继承 构造函数的 属性和方法。

Note构造函数中 this 关键字没有任何的值。
this 的值 是在函数被调用的那一瞬间, 实例化一个新对象(new object)时赋值的。

作为函数方法  调用函数

在 JavaScript 中, 函数 也是 对象

JavaScript 函数  有它自己的  属性和方法

call() 和 apply() 是函数内部 预定义的方法。 

因此,每一个函数 都自带call()和apply()方法


两个方法可用于调用函数,

注意: 两个方法的第1个参数 必须是 对象本身。


实例

function myFunction(a, b) {
    return a + b;
}
myFunction.call(myObject, 6, 7);      // 返回 13

实例

function myFunction(a, b) {
    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, 它将使用全局对象进行替代。

Note通过 call() 或 apply() 方法你可以设置 this 的值, 且作为已存在对象的新方法调用。


JavaScript 闭包


JavaScript 变量  可以是  局部变量 或 全局变量。

私有变量  可以用到   闭包。


全局变量

函数可以访问函数内部定义的变量 (局部变量),如:

实例

function myFunction() {
    var a = 5;
    return a * a;
}

函数也可以访问 函数外部  定义的变量 (全局变量),如:

实例

var a = 67;
function myFunction() {
    return a * a;
}

后面一个实例中, a 是一个 全局变量。

在web页面中  全局变量  属于  window 对象。

全局变量   可应用于页面上的所有脚本。


在第1个实例中, a 是一个 局部 变量。

局部变量  只能用于定义它函数内部。

对于其他的函数  或 脚本代码 是不可用的。


全局和局部变量即便名称相同,它们也是两个不同的变量。修改其中一个,不会影响另一个的值。

Note变量声明是如果不使用 var 关键字,那么它默认就是一个全局变量即便它在函数内定义




变量的 生命周期  

全局变量  的作用域是全局性的,即在整个JavaScript程序中,全局变量 时时处处都在。


而在函数内部声明的变量,只在函数内部起作用。

这些变量是局部变量,作用域是局部性的;

函数的参数也是局部性的,所以也只在函数内部起作用。


计数器困境

设想下如果你想统计一些数值,且该计数器在所有函数中都是可用的。

你可以使用全局变量,函数设置计数器递增:

实例

var counter = 0;

function add() {
    counter += 1;
}

add();
add();
add();

// 计数器现在为 3

计数器数值 在每次执行 add() 函数时发生+1的变化 。

如下图所示:


但问题来了,页面上的任何脚本都能直接去 改变计数器count的值,即使在没有调用 add() 函数的情况下。

如果我在在函数内声明的计数器,那么,如果没有调用函数,将无法修改计数器的值:

实例

function add() {
    var counter = 0;
    counter += 1;
}

add();
add();
add();

// 本意是想输出 3, 但事与愿违,输出的都是 1 !

以上代码将无法正确输出,因为每次我调用 add() 函数,计数器都会设置为 1。



下面,我们隆重介绍 JavaScript的 内嵌函数,它就可以完美解决该问题。


JavaScript 内嵌函数

所有函数 都能访问 全局变量。  

实际上,在 JavaScript 中,所有函数  都能访问  它们上一层的作用域。

JavaScript 支持 嵌套函数

嵌套函数 可以访问  上一层的函数变量。

该实例中,内嵌函数 innerAdd() 可以访问  父函数的 counter 变量:

实例

function add() {
    var counter = 0;
    function innerAdd() {counter += 1;}
    innerAdd();    
    return counter; 
}




如果我们能在外部访问 innerAdd() 函数,这样就能解决计数器的困境。


我们同样需要确保 counter = 0 只执行一次。


我们需要再次隆重介绍  闭包


JavaScript 闭包

还记得 函数自我调用 吗?该函数会做什么?

实例

var add = (function () {
    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 方法进行访问和修改。
外部其他任何函数都没有权力 干涉!

Note闭包 是可访问上一层父函数作用域里  变量  的函数,即便上一层函数已经关闭,不存在了。

相关文章参考

JavaScript学习笔记:学习Javascript闭包





未完待续,下一章节,つづく



评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值