js - 函数表达式

第七章 函数表达式

1. 创建函数两种方法

函数表达式特征

1,函数声明:

特征: 函数声明提升,意思执行代码前会先读取函数声明。这就意味着可以把函数声明放在调用它的语句后面。


function functionName(arg0, arg1, arg2) {
    // 函数体
}

sayHi();

function sayHi() {
    consoel.log('Hi');
}

<!-- 这个例子不会报错 -->

2,函数表达式(匿名函数(anonymous function),拉姆达函数),匿名函数的函数名称是空字符串

var fName = function(arg){
    // 函数体
}

<!-- 创建一个函数并将它赋值给变量 fName -->

3, 函数声明 和 函数表达式区别

  • 理解函数提升的关键,就是理解函数声明与函数表达式之间的区别

函数声明执行顺序: 先获取声明 -》 在执行
函数表达式: 先获取值 -》 在执行

标题 顺序 备注
函数声明 获取声明 -》 执行 null
函数表达式 获取值 -》在执行 null

通过例子理解

<a href="h-1.html">函数提升</a>

// 函数声明
fDefine('!')
function fDefine(arg0) {
    alert('函数声明' + arg0)
}

// 函数表达式
fExpression('!')
var fExpression = function(arg0) {
    alert('函数表达式' + arg0)
}

2. 递归

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

例子1

var result = factorial(3)

// 3 * (3-1) * (2-1) = 6

console.log(result)

function factorial(num){
    if (num <= 1){
        return 1;
    } else {
        return num * factorial(num-1);
    }
}


例子2 - 改进

arguments.callee 是一个指向正在执行的函数的指针,因此可以用它来实现对函数 的递归调用

function factorial(num){
    if (num <= 1){
        return 1;
    } else {
        return num * arguments.callee(num-1);
    }
}

但在严格模式下,不能通过脚本访问 arguments.callee,访问这个属性会导致错误。

例子3 - 改进

通过命名函数表达式实现相同结果

var factorial = (function f(num -1){
    if (num <=1) {
        return 1
    } else {
        return num * f(num -1)
    }
})

这种方式在严格模式和 非严格模式下都行得通。

拓展

题目:假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

斐波那契数列以如下被以递推的方法定义:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)

var fib = function (n){
  if(n == 1){
    return 1;
  }else if(n==2){
    return 2;
  }else if(n>2){
    return fib(n-1) + fib(n-2);
  }
}
console.log(fib(4));

当我们从第 n-1 阶楼梯爬到第 n 阶楼梯时,需要1步;

当我们从第 n-2 阶楼梯爬到第 n 阶楼梯时,需要2步.也就是说 到达第 n 阶楼梯的方法数等于到达第 n-1 阶楼梯的方法数加上到达第 n-2 阶楼梯的方法数,即f(n) = f(n - 1) + f(n - 2)},其正好符合斐波那契通项。

3. 闭包

闭包是一个有权访问另一个函数变量的的函数, 创建闭包的常见方式,就是在一个函数内部创建另一个函数

通过例子理解:

function compare(value1, value2) {
    if (value1 > value2) {
        return -1;
    } else {
        return -2
    }
}

var result = compare(10, 15)

上面例子发生了什么?

1, 定义了compare函数,在全局调用了它

2,调用compare() 会创建包含arguments、value1 和 value2 的活动对象

3,全局环境的变量对象(包含result 和 compare)在compare() 执行环境处于第二位。

arguments 是一个对应于传递给函数的参数的类数组对象。

zuoyongyu.png

<img src="images/zuoyongyu.png">

后台的每个执行环境都有一个表示变量的对象——变量对象。

作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。

当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象),但是闭包情况有所不同

--

function createComparisonFunction(age){

    return function(obj1, obj2){
        var value1 = obj1[age]
        var value2 = obj2[age]

        if(value1 > value2) {
            return -1;      
        } else {
            return 1
        }

    }

}

函数内部的匿名函数可以访问外部函数中的变量age,之所以可以访问外部变量,是因为内部函数作用域链包含了createComparisonFunction的作用域。

1,当某个函数被调用时,会创建一个执行环境(execution context)及相应的作用域链。 然后,使用 arguments 和其他命名参数的值来初始化函数的活动对象(activation object)

2,但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,......直至作为作用域链终点的全局执行环境。

var compare = createComparisonFunction("name");
var result = compare({ name: "Nicholas" }, { name: "Greg" });

在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中。

zuoyongyu1.png

<img src="images/zuoyongyu1.png">

(1)闭包与变量

闭包只能取得包含函数中任何变量的最后一个值。闭包所保存的是整个变量对象,而不是某个特殊的变量。

下面这段代码想要循环延时输出结果 0 1 2 3 4,请问输出结果是否正确,如果不正确,请说明为什么,并修改循环内的代码使其输出正确结果

var result = function() {
    for (var i = 0; i < 5; ++i) {
        setTimeout(function() {
            console.log(i + " ");
        }, 100);
    }
}
result()
  

输出结果为 5 5 5 5 5

原因:

1,js 运行环境为单线程,setTimeout 注册的函数需要等到线程空闲时才能执行,此时 for 循环已经结束,i 值为 5

2, 又因为循环中 setTimeout 接受的参数函数通过闭包访问变量 i,所以 5 个定时输出都是 5。

修改方法:将 setTimeout 放在立即执行函数中,将 i 值作为参数传递给包裹函数,创建新闭包

可以通过创建另一个匿名函数强制让闭包的行为,符合预期

var result = function() {
    for (var i = 0; i < 5; ++i) {
        (function (i) {
            setTimeout(function() {
                console.log(i + " ");
            }, 100);
        })(i)
        
    }
}
result()

闭包内逻辑: (闭包(num){ return next闭包() { return num } })(i) push => i 也就是num

例子2

function add() {
  var x = 1;
  console.log(++x);
}

add(); //执行输出2,

add(); //执行还是输出2

使用闭包,怎样才能使每次执行有加 1 效果呢?

function add() {
    var x = 1
    return function() {
        console.log(++x)
    }
}

var result = add()
result()
result()

(2)this对象

1, this 对象是在运行时基于函数的执行环境绑定的,

2, this 对象是在运行时基于函数的执行环境绑定的:在全局函数中,this 等于 window,而当函数被作为某个对象的方法调用时,this 等 于那个对象。

3, 匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window。

名称 描述
局部函数 基于函数的执行环境绑定
全局函数 this 等于 window
匿名函数 匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window
var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
        return function(){
            return this.name;
        };
} };
    
alert(object.getNameFunc()()); //"The Window"(在非严格模式下)

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

外部作用域中的 this 对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了

var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
        var that = this
        return function(){
            return that.name;
        };
} };
    
alert(object.getNameFunc()()); //"The Window"(在非严格模式下)

3. 模仿块级作用域

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

function outputNumbers(count){
    for (var i=0; i < count; i++){
          alert(i);
    }
    alert(i); //计数 
}
function outputNumbers(count){
    for (var i=0; i < count; i++){
        alert(i); 
    }
    var i; //重新声明变量
    alert(i); //计数 }

JavaScript 从来不会告诉你是否多次声明了同一个变量;遇到这种情况,它只会对后续的声明视而不 见

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

JavaScript 将 function 关键字当作一个函数声明的开始,而函数声明后面不能跟圆括号。然而,函数表达式的后面可以跟圆括号。

4.私有变量

function add(num1, num2){
    var sum = num1 + num2;
    return sum;
}

私有变量有哪些?

num1, num2, sum

1.私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。
2.把有权访问私有变量和私有函数的公有方法称为特权方法。

特权方法?

1,在构造函数中定义特权方法。

function MyObject(){
    var privateVariable = 10;
    function privateFunction(){
        return false;
    }
    //特权方法
    this.publicMethod = function (){
        privateVariable++;
        return privateFunction();
    };
}

能够在构造函数中定义特权方法,是因为特权方法作为闭包有权访问在构造函数中定义的 所有变量和函数。

作用?

利用私有和特权成员,可以隐藏那些不应该被直接修改的数据

function Person(name){
    this.getName = function(){
        return name;
    };
    this.setName = function (value) {
        name = value;
    }; 
}

var person = new Person("Nicholas");
alert(person.getName());   //"Nicholas"
person.setName("Greg");
alert(person.getName());   //"Greg"

4.1 静态私有变量

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

(function(){

    //私有变量和私有函数
    var privateVariable = 10;
    function privateFunction(){
        return false;
    }
    
    //构造函数
    MyObject = function(){ };
    
    //公有/特权方法
    MyObject.prototype.publicMethod = function(){
        privateVariable++;
        return privateFunction();
    };
    
})();

1,这个模式创建了一个私有作用域,并在其中封装了一个构造函数及相应的方法

2,在私有作用域中, 首先定义了私有变量和私有函数,然后又定义了构造函数及其公有方法。

3,公有方法是在原型上定义的, 这一点体现了典型的原型模式。

这个模式和构造函数定义特权方法区别?

就在于私有变量和函数是由实例共享的。由于 特权方法是在原型上定义的,因此所有实例都使用同一个函数。

(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("Nicholas"); 
alert(person1.getName()); //"Nicholas" 
person1.setName("Greg"); 
alert(person1.getName()); //"Greg"

var person2 = new Person("Michael");
alert(person1.getName()); //"Michael"
alert(person2.getName()); //"Michael"

以这种方式创建静态私有变量会因为使用原型而增进代码复用,但每个实例都没有自己的私有变 量。到底是使用实例变量,还是静态私有变量,最终还是要视你的具体需求而定。

4.2 模块模式

模块模式(module pattern)是为单例创建私有变量和特权方法。所谓单例(singleton),指的就是 只有一个实例的对象 。 按照惯例,JavaScript 是以对象字面量的方式来创建单例对象的。

var singleton = {
   name : value,
    method : function () { 
        //这里是方法的代码
    } 
};

模块模式通过为单例添加私有变量和特权方法能够使其得到增强

var singleton = function(){

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352