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 对象。
- javascript函数表达式
- Javascript之函数表达式
- javascript函数表达式
- JavaScript函数表达式
- JavaScript函数表达式
- Javascript 函数表达式
- JavaScript的函数表达式
- JavaScript笔记:函数表达式
- JavaScript函数表达式
- JavaScript函数表达式
- 5 javascript 函数表达式
- javascript(函数表达式)
- JavaScript-函数表达式
- JavaScript函数表达式
- Javascript函数表达式
- JavaScript中的函数表达式
- javascript 函数表达式
- javascript正则表达式函数详解
- java的native方法
- mysql服务器查询慢原因分析方法
- java概述
- Cookie与Session的区别
- linux命令
- Javascript 函数表达式
- 怎样把Audio cd光盘里的歌曲文件复制到硬盘里
- linux内存/cpu利用率 引出vmstat命令 free命令
- Android Shape自定义纯色圆角按钮
- JavaScript 事件
- zoj3715 Kindergarten Election 【枚举+贪心】
- 【排序算法】简单选择排序(java实现)
- Android Button及TextView动态变换颜色
- JavaScript 最佳实践