Javascript 函数表达式

来源:互联网 发布:数据库被标记为suspect 编辑:程序博客网 时间:2024/06/05 23:07

WilsonLiu’s blog 首发地址

定义函数的方式

第一:函数声明
第二:函数表达式

函数声明提升

sayHi();function sayHi(){    alert("Hello world!")}

7.1 递归

递归函数是在一个函数通过名字调用自身的情况下构成的。

function fac(num) {    if (num <=1) {        return 1;    } else {        return num * fac(num - 1);     }}var anotherFac = fac;fac = null;alert(anotherFac(4))

上述代码会报错因为,fac = null解除了fac对于函数的引用,使得只有anotherFac指向函数,可以通过

function fac(num) {    if (num <=1) {        return 1;    } else {        return num * arguments.callee(num - 1);     }}var anotherFac = fac;fac = null;alert(anotherFac([4,4]))

arguments.callee是一个指向正则执行的函数的指针。
但是use strict情况下,arguments.callee会报错。

可以使用命名函数表达式

var factory = (function fac(num) {    if (num <=1) {        return 1;    } else {        return num * fac(num - 1);     }})

7.2 闭包

闭包是指有权访问另一个函数作用域变量的函数。创建闭包的常见方式,就是在一个函数内部创建另外一个函数。
因为内部匿名函数的作用域链(一个栈结构)指向2:全局变量对象,1:包含函数变量对象,0:自己的变量对象。所以虽然随着包含函数的
执行结束,包含函数的作用域链销毁了,但是因为任然有函数的作用域链指向包含函数的变量对象,所以不会触发回收机制。

回收机制:为了及时的回收内存资源,当没有指针指向一个存储在内存中的对象时,该对象就会被回收,即销毁。

举例说明

function test(arg){    var ss = arg;    return function(opt){        console.log(ss);  //首先,在作用域链顶层指向的自己的变量对象中查找ss,没查找到,继续向上一级作用域链即包含函数变量对象中查找,找到ss =  arg,输出        console.log(opt); //输出参数opt    }}var testCache = test("name"); //创建函数,此时ss = arg ="name";testCache指向局部函数var result = testCache("test");  //调用局部函数,此时opt = "test",这个时候才会触发匿名函数内部的log

因为testCache指向内部的匿名函数(不满足回收机制),所以内部的匿名函数一直得不到销毁,test的作用域链
虽然被销毁了,但是他的活动对象始终留在内存中,直到匿名函数被销毁之后。
testCache = null //解除对匿名函数的引用(以便释放内存)

7.2.1 闭包与变量

作用域链这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值。
闭包保存的是整个变量对象,而不是某个特殊的变量。

function test(){    var result = new Array();    for (var i = 0; i < 10; i++) {        result[i] = function(){            return i;        }    }    return result;}var testCache = test();  //testCache指向resultfor (var i = 0; i < 10; i++) {    var anonymous = testCache[i]; //  testCache[i]指向result数组中对应的第i个指向的匿名函数;    console.log(anonymous());  //调用匿名函数,并输出匿名函数返回的值}

以上代码输出的都是10,并非预期的1~10;因为anonymous指向的匿名函数保存的都是test函数的活动对象,所以他们引用的都是同一个i;
当test函数调用完成并返回result之后,变量i的值为10,所以anonymous引用的都是保存变量i的同一个变量对象。

function test(){    var result = new Array();    for (var i = 0; i < 10; i++) {        result[i] = (function(num){                return function () {                    console.log(i);                    return num;                }        })(i)    }    return result;}var testCache = test();  //testCache指向resultfor (var i = 0; i < 10; i++) {    var anonymous = testCache[i]; //  testCache[i]指向result数组中对应的第i个指向的匿名函数;    console.log(anonymous());  //调用匿名函数,并输出匿名函数返回的值    // anonymous()}

第二份代码中,我们没有直接把闭包复制给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋值给数组。这里的匿名函数有一个参数num,
也就是最后函数要返回的值,num是由i按值传递的,所以每次将i的当前值给参数num,而在这个匿名函数内部,又创建并返回一个访问num的闭包。这样result数组
中的每个函数都有自己num变量的一个副本,因此可以返回各自不同的值。

7.2.2 关于this对象

匿名函数的执行环境具有全局性,因此其this对象通常指向window(可以用call()或者apply()改变指向)

每个函数在被调用的时候都会自动获取两个特殊变量:this和arguments,内部函数在搜索这两个变量时候,只会搜索到其活动对象位置,
因此永远也不可能直接访问外部函数中的这两个变量。

var name = "this window";var object = {    name: "my object",    getNameFun: function(){        return function(){            return this.name;        }    }}console.log(object.getNameFun()()); //log "this window" 匿名函数this指向全局window

如果想要上述代码中内部的返回匿名函数能够return到object里的变量值,

var name = "this window";var object = {    name: "my object",    getNameFun: function(){        var that = this;        return function(){            return that.name;        }    }}console.log(object.getNameFun()()); //log "my object"将that 指向的是object,闭包可以访问包含函数中的变量that

7.2.3内存泄露

function test(){    var el = document.getElementById('someOne');    el.onclick = function(){        alert(el.id);    }}

如上test函数里有对dom元素的引用,而闭包中又对dom元素进行了引用,这样,dom元素占用的内存始终不会被回收。

function test(){    var el = document.getElementById('someOne');    var id = el.id;    el.onclick = function(){        alert(id);    }    el = null;}

不过可以小小的改动,即可让dom被回收。

7.3 模仿块级作用域

JavaScript中没有块级作用域的概念,这意味着在块级语句中定义的变量,实际上是在包含函数中而非语句中创建,请看下例

function test(count){    for (var i = 0; i < count; i++) {        console.log(i);//输出1~count-1    }    console.log(i);//输出count}test(5)

在C++,Java等语言中,变量i只会在for循环的语句块中有定义,循环一旦结束,变量i便会被销毁。但是JavaScript中
变量i是定义在test()的活动对象中的。

因此,匿名函数可以用来模仿块级作用域。

function(){    //这里是块级作用域}()  //出错

上述代码会导致语法错误,是因为JavaScript将function关键字当做一个函数声明的开始,而函数声明后面不能跟圆括号。
然而,函数表达式的后面可以跟圆括号。要将函数声明转换成函数表达式,只需要像下面这样给他加上一对圆括号即可

(function(){    //这里是块级作用域})()

7.4私有变量

严格的说来,js中没有私有成员的概念,所以对象属性都是公有的。不过,倒是有一个私有变量的概念,
任何在函数内定义的变量,都可以认为是私有变量,因为在函数的外部无法访问到这些变量。私有变量
包括函数的参数,局部变量和在函数内部定义的其他函数。

我们把有权访问私有变量和私有函数的公有方法称为特权方法。有两种在对象上创建特权方法的方式。

7.4.1 利用构造函数定义特权方法

第一种,是在构造函数中定义特权方法,基本模式如下:

function Person(name){    var year = 2015;    this.getName = function(){        return name;    }    this.setName = function(Value){        name = Value;    }    console.log(this);}var person = new Person("example"); //实例化一个对象,log(this)是实例化的Person对象,person是指向该对象的指针

注意:如果采用Person("test");直接调用该函数,则此时this指代全局对象window,将会在window上添加上2个方法。所以必须用new
实例化对象。每次实例化的对象中,name值都不同,同时每次都会重新创建这两个函数,这个也是使用构造函数来创建特权方法的缺点。

2个方法虽然能够通过作用域链访问到year这个私有变量,但是如果year并不是出现在特权方法中,则方法的closure(闭包中没有year这个属性)。

7.4.2 静态私有变量

通过在私有作用域中定义私有变量或函数,同样也可以创建特权方法。
所以

(function(){    var name = "";    Person = function(value){        name = value;    };    Person.prototype.getName = function () {        return name;    };    Person.prototype.setName = function(value){        name = value;    };})();var person1 = new Person("test");console.log(person1.getName());

注意点:这个模式在定义构造函数时并没有使用函数声明,而是使用函数表达式。函数声明只能创建局部函数。
我们也没有在声明Person时使用var,因此Person就是一个全局变量。
与前者的区别在于私有变量和函数都是由实例共享的

7.4.3模块模式

前面的模式是用于为自定义类型创建私有变量和特权方法的。而模块模式则是为单例创建私有变量和
特权方法。所谓单例,指的就是只有一个实例对象。如下所示:

var singleton = function(){    // 私有变量和函数    var privateVariable = 10;    function privateFunction(){        return false;    }    // 特权/公有方法和属性    return {        publicProperty: true,        publicMethod: function(){            privateVariable++;            return privateFunction();        }    }}()

在web应用程序中,经常要使用一个单例来管理应用程序级的信息。

7.4.4增强的模块模式

var application = function(){    // 私有变量和函数    var privateVariable = 10;    function privateFunction(){        return false;    };    //实例化BaseComponent,app的    var app = new BaseComponent();    // 特权/公有方法和属性    app.publicProperty = true;    app.publicMethod = function(){        privateVariable++;        return privateFunction();    };    return app;}()

这种增强的模块模式适用于那些单例必须是某种类型的实例,同时还必须添加某些属性和方法对其加以增强的情况。

附录

this 的工作原理

JavaScript 有一套完全不同于其它语言的对 this 的处理机制。 在五种不同的情况下 ,this 指向的各不相同。
- 全局范围内
this
当在全部范围内使用 this,它将会指向全局对象。
- 函数调用
foo();
这里 this 也会指向全局对象。
- 方法调用
test.foo();
这个例子中,this 指向 test 对象。
- 调用构造函数
new foo();
如果函数倾向于和 new 关键词一块使用,则我们称这个函数是 构造函数。 在函数内部,this 指向新创建的对象。
- 显式的设置 this

function foo(a, b, c) {}var bar = {};foo.apply(bar, [1, 2, 3]); // 数组将会被扩展,如下所示foo.call(bar, 1, 2, 3); // 传递到foo的参数是:a = 1, b = 2, c = 3

当使用 Function.prototype 上的 call 或者 apply 方法时,函数内的 this 将会被 显式设置为函数调用的第一个参数。

常见误解

误解1

一个常见的误解是 test 中的 this 将会指向 Foo 对象,实际上不是这样子的。

Foo.method = function() {    function test() {        // this 将会被设置为全局对象(译者注:浏览器环境中也就是 window 对象)    }    test();}

为了在 test 中获取对 Foo 对象的引用,我们需要在 method 函数内部创建一个局部变量指向 Foo 对象。

Foo.method = function() {    var that = this;    function test() {        // 使用 that 来指向 Foo 对象    }    test();}

that 只是我们随意起的名字,不过这个名字被广泛的用来指向外部的 this 对象。 在 闭包 一节,我们可以看到 that 可以作为参数传递。

误解2

另一个看起来奇怪的地方是函数别名,也就是将一个方法赋值给一个变量。

var test = someObject.methodTest;test();

上例中,test 就像一个普通的函数被调用;因此,函数内的 this 将不再被指向到 someObject 对象。

0 0
原创粉丝点击