js中的函数表达式
发布于 4 个月前 作者 chao65 3162 次浏览 来自 分享

1 函数表达式

创建函数有两种方式 函数声明 与 函数表达式

函数声明

// 函数声明 函数声明会有函数提升即可以先使用,后声明

f();
function f(){
    console.log('函数提升')
}

函数表达式

函数表达式有几种方式,最常用的是函数字面量形式

1.1 递归

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

这是一个求阶乘的递归函数

function factorial(num){
    if(num <= 1){
        return num
    } else {
        return num * factorial(num - 1)
    }
}
console.log(factorial(5))

但是这样却可能报错

let anotherFactorial = factorial;
factorial = null;
console.log(anotherFactorial(3)) // error: factorial is not a function

这是由于factorial已经等于null了,而递归函数内部仍然调用的是 num * factorial(num - 1)

// argumrnts.callee指向正在执行的函数的指针
function factorial(num){
    if(num <= 1){
        return num;
    } else {
        return num * arguments.callee(num-1)
    }
}
// 这样就不会报错了
let anotherFactorial = factorial;
factorial = null;
console.log(anotherFactorial(3)) 

1.2 闭包

闭包是指有权访问另一个函数作用域中的变量 的函数

// 闭包
let dad = {
    age: 10
}
let mom = {
    age: 40
}
function createComparisonFunction(propertyName){
    // 返回一个匿名函数
    return function(object1 , object2){
        let value1 = object1[propertyName]
        let value2 = object2[propertyName]
        if(value1 > value2){
            return ">"
        }
        return '<='
    }
}

// 当匿名函数被返回后,他的作用域链会包含createComparisonFunction的活动对象和全局变量对象
let compare = createComparisonFunction('age') // 这时候compare就是返回的匿名函数
let result = compare(dad , mom)  // compare里还可以访问createComparisonFunction 里面的 propertyName
console.log(result)
// 由于闭包会携带包含它的函数的作用域,所以会比其他函数占用更多的内存

1.2.1 闭包与变量

作用域链的这种配置会引出一个缺陷,即闭包只能取得包含函数中任何变量的最后一个值
// 作用域链的而这种配置机制引出一个副作用,即闭包只能取得包含函数中任何变量的最后一个值

function createFunction(){
    let result = [];
    for(var i = 0 ; i < 10 ; i++){
        result[i] = function(){
            return i;
        }
    }
    return result
}

// 表面上看,返回的是一个函数数组,数组里的每个元素都会返回i
// 但实际上每个都会返回10
// 因为var声明的变量不存在块级作用域,实际上每个函数都包含 createFunction()的活动对象,他们引用的都是同一个变量i,而当createFunction()返回后,i的值是10
console.log(createFunction()1) // 10
console.log(createFunction()2) // 10

// 我们可以通过创建一个匿名函数强制让强制让闭包的行为符合预期
function createFunction(){
    let result = [];
    for(var i = 0 ; i < 10 ; i++){
        result[i] = function(num){
            return function(){
                return num
            }
        }(i)
    }
    return result;
}
console.log(createFunction()[1]())

1.2.2 关于this对象

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

不过匿名函数的执行环境具有全局性,因此this通常指向window,
但有时候编写闭包的方式不同,这一点可能不那么明显

let name = 'my window'
let obj = {
    name: 'my obj',
    say: function(){
        return this.name
    }
}
console.log(obj.say())  // 'mu obj'

1.2.3 内存泄漏

function assinHandler(){
    let element = document.getElementById("dom")
    element.onclick = function(){
        alert(element.id)
    }
}
assinHandler()
// 以上代码创建了一个作为element元素事件处理的闭包,而这个闭包又创建了一个循环引用。由于匿名函数保存了一个对assinHandler的活动对象
// 的引用,所以无法减少element的引用数。只要匿名函数存在,element的引用至少为1,所以它占用的内存就用愿不会被回收
// 闭包会包含函数的整个活动对象,所以即使闭包不直接引用element,包含的活动对象中仍然会保存一个引用

// 修改
function assinHandler(){
    let element = document.getElementById("dom");
    let id = element.id; // element.id 保存在一个变量中
    element.onclick = function(){
        alert(id)
    }
    element = null; // 解除对element对象的引用,
}

1.3 模仿块级作用域

es6已有块级作用域的概念

1.4 私有变量

严格讲,js中没有私有成员的概念,所有对象属性都是公有的。
不过在函数中定义的变量,都可以认为是私有变量。因为在函数外部不能访问他们。

我们把有权访问私有变量和私有函数的共有方法称为特权方法,而创建特权方法的的方式有两种

1.4.1 在构造函数中定义特权方法

// 通过构造函数创建特权方法
function Person(name){
    this.getName = function(){
        return name;
    }
    this.setName = function(value){
        name = value
    }
}
let p = new Person('jack')
console.log(p.getName())

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

(function(){
    var name = 'wu';
    Person = function(value){ // 未经声明的变量,总会创建一个全局变量
        name = value
    }
    Person.prototype.getName = function(){
        return name;
    }
    Person.prototype.setName = function(value){
        name = value
    }
})()
let p = new Person('wwww');
console.log(p.getName())

1.4.2 模块模式

前面的模式是用于为自定义类型创建私有变量和特权方法的,这里的模块是为单例创建私有变量
单例:指的是只有一个实例的变量,通常js中用对象字面量的方式来创建单例对象

let singleObj = {
    name: 'singlr',
    method: function(){
        consol.log('sss')
    }
}

如果必须创建一个对象,并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么就可以使用模块模式

let app = function(){
    let component = new Array();
    return {
        setCom: function(item){
            component.push(item)
        },
        getCom: function(){
            return component
        }
    }

}()
console.log(app.getCom())
app.setCom('sss')
console.log(app.getCom())

1.4.3 增强的模块模式

在返回对象之前加上对其增强的代码。这种增强模式适合那些单例必须是某种类型的实例,同时还必须还添加某些属性或方法对其加以增强的方法。
// 增强模块

function APP(){

}
let singleton = function(){
    let component = new Array();  // 私有变量
    let app = new APP();  // 创建对象 这个对象是APP的实例
    app.getCom = function(){
        return component
    }
    app.setCom = function(val){
        component.push(val)
    }
    return app;//返回这个对象
}()
singleton.setCom('new');
console.log(singleton.getCom())

1.5 小结

函数表达式无需对函数命名,从而实现动态编程。
匿名函数,是一种使用js编程的强大方式,一下是函数表达式的特点。

  1. 函数表达式不同于函数声明,函数声明需要名字,函数表达式不需要。没有名字的函数表达式也叫匿名函数。
  2. 在无法确定如何引用函数的情况下,递归函数就会变得复杂
  3. 递归函数应该始终使用arguments.callee(),因为函数名可能会变化
    当在函数内部创建其他函数时,就创建了闭包,闭包有权访问 包含它的函数 内部的所有变量,原理如下:
  4. 在后台执行环境中,闭包的作用域包含 自己的作用域 包含它的函数作用域 全局作用域
  5. 通常,函数作用域及其所有变量都会在函数执行结束后被销毁。
  6. 但是,当函数返回了一个闭包时,这个函数的作用域会在内存中保存到闭包不存在为止
    使用闭包可以在js中模仿块级作用域,要点如下:
  7. 创建并立即执行一个函数,这样既能执行其中代码,又不会在内存中留下对该函数的引用
  8. 结果就是函数内部所有变量会立即销毁–除非将某些变量赋值给了 包含作用域(即外部作用域)中的变量

闭包还可以用来在对象中创建私有变量,相关概念如下:

  1. 即使js中没有正式的私有对象属性的概念,但可以用闭包来实现公有方法,而通过公有方法可以访问包含在函数作用域中定义的变量
  2. 有权访问私有变量的方法叫特权方法
  3. 可以使用构造函数模式 原型模式来实现自定义类型的特权方法,也可以使用模块模式,增强的模块模式来实现单例的特权方法
回到顶部