码迷,mamicode.com
首页 > 编程语言 > 详细

Javascript 函数表达式

时间:2016-04-29 16:13:48      阅读:245      评论:0      收藏:0      [点我收藏+]

标签:

定义函数的方式

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

函数声明提升

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指向result
for (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指向result
for (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 对象。

Javascript 函数表达式

标签:

原文地址:http://blog.csdn.net/liusheng95/article/details/51263275

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!