js - 函数表达式

作者: guotanxiangg | 来源:发表于2019-11-21 14:57 被阅读0次

第七章 函数表达式

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();
        }
        
    };
}();

相关文章

  • js ------ 函数声明和函数表达式

    js创建对象几种方式 js 函数声明 和 函数表达式函数声明 函数表达式 函数表达式: 下面的函数都会直接运行

  • 函数表达式

    以下内容总结自《JS高级程序设计》第三版 什么是函数表达式? 函数表达式,是JS中定义函数的一种方式。在JS中,共...

  • JavaScript基础学习笔记(二)

    函数定义 函数的声明 函数表达式 JS函数可以通过一个表达式定义,函数表达式可以存储在变量中。当存储在变量中之后,...

  • 函数

    一、问答 函数声明和函数表达式有什么区别? Js中的函数声明是指下面的形式: 函数表达式则是类似表达式那样来声明一...

  • 一篇文章告诉你JS函数表达式所有特点

    函数表达式特点 在JS编程中,函数表达式是一种非常有用的技术。使用函数表达式可以无需对函数命名,从而实现动态编程。...

  • 函数与作用域

    1.函数声明和函数表达式有什么区别?Javascript 中函数声明和函数表达式是存在区别的,函数声明在JS解析时...

  • eval介绍

    eval 的定义和用法 如果参数是表达式,eval()函数会执行表达式;如果参数是 js 语句,eval()函数会...

  • JS函数与作用域

    函数声明和函数表达式有什么区别 使用函数声明时,在执行js语句的时候,会先把函数提升到js语句的顶部,所以即使函数...

  • JS函数表达式(JS高级程序设计笔记)

    函数表达式 JS定义函数的方式有两种:一种是函数声明,另一种就是函数表达式。函数声明的语法是这样的。 函数声明,它...

  • 那些年成为node攻城狮的路(七)

    函数 函数声明和函数表达式 *函数中的几个属性 arguments 在node.js中为{0:value1.......

网友评论

    本文标题:js - 函数表达式

    本文链接:https://www.haomeiwen.com/subject/jkltwctx.html