js之函数

内容来自js花园

函数声明与函数赋值表达式:
函数声明:  function foo(){};
函数赋值表达式: var foo=function(){};
函数声明会在执行前被解析,因此函数存在于当前上下文的任意地方,即可以在函数体声明前调用函数。
eg   foo();
   function foo(){};

对于函数赋值表达式,它把一个匿名函数赋值给了一个变量foo,var定义了一个声明语句,对foo的解析是在后面的函数体代码运行之前,故foo在代码运行时已经被定义过了。但由于赋值语句只在运行时执行,故赋值语句执行前,foo为默认的undefined.
eg   foo; //undefined
  foo(); //出错:TypeError
  var foo=function(){}

命名函数的赋值表达式:
一种特殊情况是将命名函数赋值给一个变量
var foo = function bar(){
bar(); //正常运行
}
bar(); //出错:ReferenceError
bar在函数声明外是不可见的,因为已经将函数赋值给了foo,但bar在函数内部是可见的,这是由于javascript的命名处理处致的,函数名在函数内部总是可见的。

this的工作原理:
有5种不同情况this的指向各不相同:
1 全局范围内时,指向全局对象
eg.           this;
2   函数调用,指向全局对象
eg.       foo();
3 方法调用,指向调用对象
eg.         test.foo();
4   调用构造函数,构造函数内部,this指向新创建的对象
eg.         new foo();
若函数倾向于与new一起使用,称其为构造函数,其内部this指向新创建的对象。
5   显式设置this
function foo(a,b,c){}
var bar={};
foo.apply(bar,[1,2,3]);
foo.call(bar,1,2,3);
当使用Function.prototype上的call或apply方法时,函数内的this显式设置为函数调用的第一个参数
常见误解:
Foo.method = function(){
function test(){
// 在这里,this指向全局对象,在浏览器环境下就是window对象
}
test();
}
为在test中获取对Foo对象的引用,需在method函数内创建局部对象指向Foo对象
Foo.method = function(){
var that = this; //that为随意命名的,也可以是任意名字,eg.  jeff
function test(){
// 用that指向Foo对象
}
test();
}

闭包和引用:
当前作用域总能访问外部作用域中的变量,函数是javascript中唯一拥有自身作用域的结构,因此闭包的创建依赖于函数
模拟私有变量:
function Counter(start) 
{ var count = start;
 return { 
 increment: function() { count++; },
 get: function() { return count; } 
 
 var foo = Counter(4);
 foo.increment(); 
foo.get(); // 5
Counter函数返回两个闭包,函数increment和get,这两个函数都维持着对外部作用域Counter的引用

不可在外部访问私有变量:
因为js中不可对作用域引用或赋值,所以无法在外部访问变量count,只能通过闭包increment和get
var foo = new Counter(4);
foo.hack = function(){
count =38012;
}
上面的代码不会改变定义在Counter作用域中的count变量的值,因为foo.hack没有定义在那个作用域内,它将创建或覆盖全局变量count

循环中的闭包:
for(var i=0;i<10;i++)
{
setTimeout(function(){console.log(i);},1000);
}
上面的代码会输出数字“10”十次
当console.log调用的时候,匿名函数保持对变量i的引用,此时for循环已经结束,i的值被改成10,为了得到想要的结果,必须在每次循环中创建变量i的拷贝。

eg. for(var i=0;i<10;i++)
{
(function(e){
setTimeout(function(){console.log(e);},1000);
})(i);
外部的匿名函数立即执行,并把i作为参数,此时e 获得了i的拷贝
程序也可以写成下面这样:
for(var i=0;i<10;i++)
{
setTimeout((function(e){return function(){console.log(e);}})(i),1000);
}

arguments对象:
   arguments是一个对象,不是数组,无法使用push,pop,slice等数组方法,但可用for循环遍历。
   转化为数组:
eg.   Array.prototype.slice.call(arguments);
上述式子将创建一个数组,包含其中所有元素,但这个转化较慢
传递参数:
将参数从一个函数传递到另一个函数,推荐做法如下:
function foo(){
bar.apply(null,arguments); //apply的第一个参数仅用于替换调用函数的this,null即代表空
}
function bar(a,b,c){
//do stuff here
}
另一个技巧是同时使用call和apply,以创建一个快速的解绑定包装器

function Foo() {}

Foo.prototype.method = function(a, b, c) {
    console.log(this, a, b, c);
};

// Create an unbound version of "method" 
// 输入参数为: this, arg1, arg2...argN
Foo.method = function() {

    // 结果: Foo.prototype.method.call(this, arg1, arg2... argN)
    Function.call.apply(Foo.prototype.method, arguments);
};
译者注:上面的 Foo.method 函数和下面代码的效果是一样的:

Foo.method = function() {
    var args = Array.prototype.slice.call(arguments);
    Foo.prototype.method.apply(args[0], args.slice(1));
};

上面这段现在还不太懂

自动更新:
改变形参的值会改变arguments对象的值,因为arguments为其内部属性和函数形参创建了getter和setter方法
eg. function foo(a,b,c){
arguments[0] = 2;
a; //2
}
foo(1,2,3);

构造函数:
通过new关键字方式调用的函数被认为是构造函数,新创建对象的prototype被指向构造函数的prototype,若被调用的函数没有显式return表达式,则隐式地返回this对象。
显式的return表达式影响返回结果,仅限于返回的是一个对象
function bar(){
return 2;
}
new bar();  //返回新创建的对象
function test(){
this.value =2 ;
return {
foo:1
};
}
new test(); //返回的对象
但如果new被遗漏了,函数不会返回新创建的对象
function foo(){
this.bla =1;
}
foo();//undefined
虽然上例在有些情况下也能正常运行,但是由于javascript中this的工作原理,这里的this指向全局对象

工厂模式:
为了不使用this关键字,构造函数必须显式返回一个值:
function bar(){
var value=1;
return{
method:function(){
return value;
}
}
}
bar.prototype = {
foo:function(){}
};

new bar();
bar();
上面两种对bar函数的调用返回的值完全相同,一个新创建的具有method属性的对象被返回,其实这里创建了一个闭包。
new bar()不会改变返回对象的原型,即返回对象的原型不会指向bar.prototype,构造函数的原型会指向刚刚创建的新对象,而bar没有把这个新对象返回,而是返回了包含method属性的自定义对象。
上面两种方式创建的对象不能访问bar原型链上的属性:
var bar1 = new Bar();
typeof(bar1.method); // "function"
typeof(bar1.foo); // "undefined"

var bar2 = Bar();
typeof(bar2.method); // "function"
typeof(bar2.foo); // "undefined"

通过工厂模式创建新对象:
忠告:不要使用new 关键字调用函数,因为如果忘记使用它会导致错误
为了创建新对象,创建一个工厂方法,并在方法内构造一个新对象。
function foo(){
var obj={};
obj.value = 'blub';
var private =2;
obj.someMethod=function(value){
this.value = value;
}
obj.getPrivate = function(){
return private;
}
return obj;
}
上面的方式有如下缺点:
1 会占用更多内存,因新创建的对象不能共享原型上的方法
2 为实现继承,工厂方法需从另外一个对象拷贝所有属性,或把一个对象作为新创建对象的原型
3 放弃原型链仅仅是因为防止遗漏new带来的问题,这似乎与语言本身思想违背
总结,使用原型链方式或工厂方式主要看应用程序的需求

作用域与命名空间:
隐式的全局变量:
foo='www';
var foo='www';
前一句在全局作用域内定义了变量foo,后一句在当前作用域定义了变量foo
eg.
var foo=42;
function test(){
foo=21;
}
test();
foo;//21
test函数内未用var关键字声明的foo变量会覆盖外部同名变量
// 全局作用域
var items = [];
for(var i = 0; i < 10; i++) {
    subLoop();
}

function subLoop() {
    // subLoop 函数作用域
    for(i = 0; i < 10; i++) { // 没有使用 var 声明变量
        // 干活
    }
}
外部循环在第一次调用 subLoop 之后就会终止,因为 subLoop 覆盖了全局变量 i。 在第二个 for 循环中使用 var 声明变量可以避免这种错误。 声明变量时绝对不要遗漏 var 关键字,除非这就是期望的影响外部作用域的行为。

局部变量:
js中局部变量只能通过两种方式声明,一是作为函数参数,另一个是通过var关键字声明

变量声明提升:
javascript会提升变量声明,这意味着var表达式和function声明都将提升到当前作用域的顶部
bar();
var bar = function() {};
var someValue = 42;

test();
function test(data) {
    if (false) {
        goo = 1;

    } else {
        var goo = 2;
    }
    for(var i = 0; i < 100; i++) {
        var e = data[i];
    }
}
上面代码在运行之前将会被转化。JavaScript 将会把 var 表达式和 function 声明提升到当前作用域的顶部。

// var 表达式被移动到这里
var bar, someValue; // 缺省值是 'undefined'

// 函数声明也会提升
function test(data) {
    var goo, i, e; // 没有块级作用域,这些变量被移动到函数顶部
    if (false) {
        goo = 1;

    } else {
        goo = 2;
    }
    for(i = 0; i < 100; i++) {
        e = data[i];
    }
}

bar(); // 出错:TypeError,因为 bar 依然是 'undefined'
someValue = 42; // 赋值语句不会被提升规则(hoisting)影响
bar = function() {};

test();

没有块级作用域不仅导致 var 表达式被从循环内移到外部,而且使一些 if 表达式更难看懂。
在原来代码中,if 表达式看起来修改了全部变量 goo,实际上在提升规则被应用后,却是在修改局部变量。
如果没有提升规则(hoisting)的知识,下面的代码看起来会抛出异常 ReferenceError。

// 检查 SomeImportantThing 是否已经被初始化
if (!SomeImportantThing) {
    var SomeImportantThing = {};
}
实际上,上面的代码正常运行,因为 var 表达式会被提升到全局作用域的顶部。

var SomeImportantThing;

// 其它一些代码,可能会初始化 SomeImportantThing,也可能不会

// 检查是否已经被初始化
if (!SomeImportantThing) {
    SomeImportantThing = {};
}

// 译者注:来自 Nettuts+ 的一段代码,生动的阐述了 JavaScript 中变量声明提升规则
var myvar = 'my value';  

(function() {  
    alert(myvar); // undefined  
    var myvar = 'local value';  
})();  

名称解析顺序:
JavaScript 中的所有作用域,包括全局作用域,都有一个特别的名称 this 指向当前对象。
函数作用域内也有默认的变量 arguments,其中包含了传递到函数中的参数。
比如,当访问函数内的 foo 变量时,JavaScript 会按照下面顺序查找:

1 当前作用域内是否有 var foo 的定义。
2 函数形式参数是否有使用 foo 名称的。
3 函数自身是否叫做 foo。
4 回溯到上一级作用域,然后从 #1 重新开始。

命名空间:
只有一个全局作用域导致的常见错误是命名冲突。在 JavaScript中,这可以通过 匿名包装器 轻松解决。

(function() {
    // 函数创建一个命名空间

    window.foo = function() {
        // 对外公开的函数,创建了闭包
    };

})(); // 立即执行此匿名函数
匿名函数被认为是 表达式;因此为了可调用性,它们首先会被执行。

( // 小括号内的函数首先被执行
function() {}
) // 并且返回函数对象
() // 调用上面的执行结果,也就是函数对象
有一些其他的调用函数表达式的方法,比如下面的两种方式语法不同,但是效果一模一样。

// 另外两种方式
+function(){}();
(function(){}());
结论

推荐使用匿名包装器(即自执行的匿名函数)来创建命名空间。这样不仅可以防止命名冲突, 而且有利于程序的模块化。

另外,使用全局变量被认为是不好的习惯。这样的代码倾向于产生错误和带来高的维护成本。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值