函数表达式

Friday, August 16, 2019

第七章 函数表达式

7.1 函数表达式

    1. 定义函数的方式有两种

      1. 函数声明 function functionName(){}

        1. 大部分浏览器支持 functionName.name 返回 functionName;
        2. 函数声明提升,再执行代码前会先读取函数声明;
      2. 函数表达式 var functionName = function(){};

        1. 创建一个函数赋值给变量,该函数称为匿名函数,function 后面没有标识符;
    1. 调用匿名函数时,需要加上(),如 functionName(),匿名函数最后直接加入()则可以自动执行,()中可以填入参数,调用函数内部返回的匿名函数,则或许要 functionName()()// 两个括号;
    2. 递归 调用函数自身

7.2 闭包

    1. 匿名函数是指没有标识符的函数,闭包是指有权访问到外部变量(另一个作用域中的变量)的函数

    2.

      1. 当某个函数被调用时,会创建一个执行环境及对应的作用域链;
      2. 然后,使用 arguments 和其它命名参数来初始化函数的活动对象;
      3. 在作用域链中,外部函数的活动对象始终处于第二位,第三位于以此类推,终点位全局执行环境;
    1. 闭包会占用更多的内存,建议只在绝对必要时使用;

      avatar

过程

  1. 创建 compare()函数时,先创建预先包含全局变量对象的作用域链,这个作用域链保存在内部的[[scope]]属性中;

  2. 调用 compare()函数时,会创建一个执行环境,复制函数[[scope]]属性中的作用域链;

  3. 此后,又有一个活动对象(自身变量对象)被推入作用域链的前端;

  4. 作用域链本身是指向变量对象的指针列表,只引用不实际包含;

  5. 访问函数中的变量时,就会从作用域链中搜索,一般函数执行完毕后,局部活动对象会被销毁,内存仅保留全局作用域(全局执行环境的变量对象);

avatar

过程

  1. 在一个函数内部定义的函数,会将它的外部函数的活动对象添加到作用域链中;
  2. 当外部函数执行完后,它的作用域链会被销毁,但它的活动对象并不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象,所以该活动对象仍然会留在内存中,直到匿名函数被销毁(匿名函数=null);

7.2.1 闭包与变量

    1. 闭包只能取得外部函数中任何变量的最后一个值;
function createFunctions() {
    var result = new Array()
    for (var i = 0; i < 10; i++) {
        result[i] = function () {
            return i
        }
    }
    return result
}
var funcs = createFunctions()
//every function outputs 10
for (var i = 0; i < funcs.length; i++) {
    document.write(funcs[i]() + '<br />')
}

该例子中,最后取得数组值都是 10,最后执行时,此时的 i 已经累加到 10,引用的是同一个 i;

function createFunctions() {
    var result = new Array()
    for (var i = 0; i < 10; i++) {
        result[i] = (function (num) {
            return function () {
                return num
            }
        })(i)
    }
    return result
}
var funcs = createFunctions()
//every function outputs 10
for (var i = 0; i < funcs.length; i++) {
    document.write(funcs[i]() + '<br />')
}

该例子中,因为循环时,最后传入了参数 i(num),并且传入参数属于值传递,所以每一个的值的副本都不相同,最后数组值也均不相同;

7.2.2 this 对象

    1. 闭包中使用 this 可能会导致某些问题,如匿名函数的执行环境具有全局性,当它在全局环境下执行时,this=window
    2. 防止该情况可以在外部函数中将外部函数的 this 赋值给某个变量,然后在匿名函数中使用该变量;
  • e.g var that = this ; return that.name;

7.2.3 内存泄漏

7.3 模仿块级作用域

    1. js 中没有块级作用域(for 之类),但函数是有作用域的,可以模仿块级作用域。
function(){
//作用域
}();

这样写会报错,因为 js 将 function 关键字当作函数声明的开始,而函数声明后不能跟圆括号,而函数表达式可以,所以要将其转换为函数表达式;

;(function () {
    //作用域
})()

7.4 私有变量

    1. 严格而言,js 没有私有变量,所有对象属性都是共有的,但函数中定义的变量,可以认为是私有变量,因为外部不能访问;

    2. 有权访问私有变量和私有函数的共有方法称作特权方法

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"
 通过构造函数中定义特权方法

 getName()和setName()是特权方法(闭包)可以访问外部变量(name),这样的设置可以避免数据被直接修改,只有通过特权方法才能更改私有变量;

 与其它构造函数模式相同,每一次生成新的实例时,都会重新创建这两个方法,而且必须通过构造函数模式来使用;

7.4.1 静态私有变量

    1. 通过在私有作用域中定义私有变量或函数,也可以创建特权方法;
    2. 通过原型定义公有(特权)方法;
    3. 私有作用域中的构造函数,并没有使用函数声明(函数声明只能创建局部函数),为了创建全局的构造函数,函数表达式中也没有使用 var;
;(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"

和原型模式一样,一个实例上调用 setName()会影响所有实例;

7.4.2 模块模式

    1. 模块模式用于为单例创建私有变量的特权方法,单例指的是只有一个实例的对象;

    2. 匿名函数返回了一个字面量对象;

function BaseComponent() {}
function OtherComponent() {}
var application = (function () {
    //private variables and functions
    var components = new Array()
    //initialization
    components.push(new BaseComponent())
    //public interface
    return {
        getComponentCount: function () {
            return components.length
        },
        registerComponent: function (component) {
            if (typeof component == 'object') {
                components.push(component)
            }
        },
    }
})()
application.registerComponent(new OtherComponent())
alert(application.getComponentCount()) //2

7.4.3 增强的模块模式

    1. 返回了 BaseComponet 的实例对象,对于某些要求单例必须是某种类型的实例需求有用;
function BaseComponent() {}
function OtherComponent() {}
var application = (function () {
    //private variables and functions
    var components = new Array()
    //initialization
    components.push(new BaseComponent())
    //create a local copy of application
    var app = new BaseComponent()
    //public interface
    app.getComponentCount = function () {
        return components.length
    }
    app.registerComponent = function (component) {
        if (typeof component == 'object') {
            components.push(component)
        }
    }
    //return it
    return app
})()
alert(application instanceof BaseComponent)
application.registerComponent(new OtherComponent())
alert(application.getComponentCount()) //2