关于函数声明

他的一个重要特征就是函数声明提升,就是在执行代码之前会先读取函数声明,这意味着可以把函数声明放到调用他的语句的后面

sayHi(); //将声明放到了后面
function sayHi(){
    console.log("hi");
}

关于函数表达式

创建一个匿名函数然后赋值给一个变量

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

可以返回一个匿名函数,返回的函数可以赋值给一个变量,也可以被其他方式调用

    function test(name,age){
        //这样可以返回一个匿名函数
        return function (name,age) {
            console.log(name);
            console.log(age);
        }
    }

递归

    //递归,不断调用自身
    function factorial(num) {
        if (num <= 1) {
            return 1;
        } else {
            //arguments.callee指向正在执行的函数指针,代替函数名
            //所以即使后面将函数设置为 null, 不影响递归
            return num * arguments.callee(num - 1);
        }
    }
    //将函数赋值给变量
    var anotherFactorial = factorial;
    factorial = null;//设置函数factorial 为 null
    console.log(anotherFactorial(4));// 返回24

不过这种方式在严格模式不能使用arguments.callee,需要借助命名函数表达式

//创建一个f 命名的函数表达式,然后将它赋值给变量factorial
    var factorial = (function f(num) {
        if (num <= 1) {
            return 1;
        } else {
            return num * f(num - 1); //函数f 依然有效
        }
    });
    console.log(factorial(4));//返回24

因为函数是引用类型,即使赋值给变量,也只是赋值了一个引用指针,所以函数本身还是可以直接使用的

闭包

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

  • 创建闭包的常见方式,就是在一个函数内部创建另一个函数

  • 闭包也可以理解为一些被返回的匿名函数,这些匿名函数可以访问另一个函数作用域

  • 闭包的作用域包含着他自己的作用域,包含函数的作用域和全局作用域

一般函数作用链细节:

  • 当某个函数第一次被调用时,会创建一个执行环境,以及相应的作用域链,并把作用域链赋值给一个特殊的内部属性[Scope]

  • 然后,使用 this,arguments 和其他命名参数的值来初始化函数的活动对象

  • 在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,如此类推,直至作为作用域链终点的全局执行环境

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

这个是一般的函数,非闭包

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

图片描述

1.每个执行环境都有一个表示变量的对象-变量对象
2.全局环境的变量对象始终存在
3.像 compare 函数这样的局部环境的变量对象只会在函数执行过程中存在
4.在创建 compare 函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[Scope]属性中
5.如果又有新的活动对象(函数执行),那么就会被推入作用域链的最前端
6.无论什么时候在函数中访问一个变量,就会从作用域链中搜索具有相应名字的变量

闭包作用链细节:

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

  • 内部定义的匿名函数的作用域链实际上会包含外部函数的活动对象

  • 即使外部函数执行完毕,内部匿名函数依然不会被销毁,然后他依然引用这个活动对象,直至这个匿名函数被销毁后才会全部销毁

    function createComparisonFunction(propertyName) {
        return function (object1, object2) {
            var value1 = object1[propertyName];
            var value2 = object2[propertyName];
            if (value1 < value2) {
                return -1;
            } else if (value1 > value2) {
                return 1;
            } else {
                return 0;
            }
        }
    }
    var compare = createComparisonFunction("name");
    var result = compare({name:"gg"},{name:"bb"});

1.首先:内部的匿名函数会添加到createComparisonFunction的作用域链中,()即执行正常的一般函数作用域链流程,只是全局活动对象改成了局部活动对象
2.然后:被返回后,这个匿名函数的作用域链被初始化为包含createComparisonFunction函数的活动对象和全局活动对象,因为被返回到外部,外部是一个全局活动对象,所以他的作用域链就有了全局活动对象
3.那么,现在如果createComparisonFunction执行完毕了,但是因为他这个活动对象还被匿名函数引用,所以即使他执行完毕了,依然不会销毁,除非引用的匿名函数销毁后,才会销毁

可以手动将被返回的匿名函数(这就是一个闭包)设置 null 来销毁

    var compare = createComparisonFunction("name");
    var result = compare({name:"gg"},{name:"bb"});
    compare = null;

将compare(他是被返回的那个匿名函数)的引用解除,就可以通知 gc 进行回收

闭包与变量

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

    function createFunctions() {
        var result = new Array();
        for (var i = 0; i < 10; i++) {
            result[i] = function () { //这是一个闭包
                //因为闭包保存的是整个createFunctions变量对象,所以当他执行完成的时候(for循环结束),
                //i是等于10的,所以就会是10,由始至终,闭包里面引用的都是一整个变量对象,而不是一个变量
                return i;
            };
        }
        return result; //返回的是一个数组,数组里面每一个项目都是一个function
    }
    var test = createFunctions();
    for (var i = 0; i < 10; i++) {
        //需要执行这个 function 才能够获取里面的值
        console.log(test[i]());//都是10 
    }

如果想达到我们的效果,返回的 i 是0-9的话:

    function createFunctions() {
        var result = new Array();
        for (var i = 0; i < 10; i++) {
            result[i] = function (num) {//这是一个匿名函数,参数是 num
                return function () {// 这是一个闭包,不过这个闭包访问的num是我们传入的num,即使闭包保存一整个变量对象,但是我们将变量对象改成了外面这个匿名函数
                    return num;     //相当于在闭包外面包了一层活动对象,将活动对象改变成能够改变值 num的活动对象
                };
            }(i);//这个匿名函数会立即执行,并且传入了 i 作为 num
        }
        return result; //返回的是一个数组,数组里面每一个项目都是一个function
    }
    var test = createFunctions();
    for (var i = 0; i < 10; i++) {
        //需要执行这个 function 才能够获取里面的值
        console.log(test[i]());//返回0-9
    }

关于 this 对象

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

  • 在通过 call() 或 apply()改变函数执行环境的时候,也会改变 this 指向

    var name = "the window";
    var object = {
        name: "my object",
        //返回一个匿名函数
        getNameFunc: function () {
            //匿名函数里面又返回一个匿名函数(闭包)
            //当返回的时候,当前环境已经变成了全局环境了,搜索的话直接在当前活动对象(全局)找到了
            return function () {
                return this.name; //所以返回的是全局环境的 name
            }
        }
    };
    //object.getNameFunc()返回一个函数,然后再加上(),这个函数就会执行
    console.log(object.getNameFunc()());//返回 the window

如果对 this 进行保存的话,那么可以将当前对象的引用保存起来,赋值到一个变量上来使用

    var name = "the window";
    var object = {
        name: "my object",
        //返回一个匿名函数
        getNameFunc: function () {
            //将当前对象(当前这个function)引用保存起来
            var that = this;
            //匿名函数里面又返回一个匿名函数(闭包)
            //当返回的时候,使用的是保存起来的这个对象引用,所以会返回这个对象的属性
            return function () {
                return that.name;
            }
        }
    };
    //object.getNameFunc()返回一个函数,然后再加上(),这个函数就会执行
    console.log(object.getNameFunc()());//返回 my object

很多时候很有用,在 this 可能会被改变的情况下(尤其在闭包环境下),先保存起来会很方便

下面例子是想说明语法的细微变化,也可能改变this的值

    var name = "the window";
    var object = {
        name: "my object",
        getName: function () {
            return this.name;
        }
    };
    //正常执行,返回正常
    console.log(object.getName());//返回 my object
    //加了括号,但是this没有改变
    console.log((object.getName)());//返回 my object
    //加了赋值运算,this被改变了,因为赋值表达式的值是函数本身,处于全局环境下
    console.log((object.getName = object.getName)());//返回 the window

模仿块级作用域

  • javascript 没有块级作用域(私有作用域)的概念

  • 匿名函数可以模仿块级作用域

    (function () {
      //块级作用域
    }());
---------分解一下
    ( //第一对圆括号
    function () {
      //块级作用域  
    }
    ()//第二对圆括号
    );
    //将函数声明包含在第一对中,表示他实际上是一个函数表达式
    //而紧随其后的第二对圆括号会立即调用这个函数
  • 无论什么地方,只要临时需要一些变量,就可以使用,这种技术经常在全局作用域中被用在函数外部,从而限制响全局作用域中添加过多的变量和函数

  • 这种做法可以减少闭包占用内存的问题,因为没有指向匿名函数的引用,只要函数执行完毕,就可以立即销毁其作用域链

私有变量

  • 把有权访问私有变量和私有函数的共有方法称为特权方法
    第一种创建方法:

    function MyObject() {
        //私有变量和私有函数
        var privateVariable = 10; 
        function privateFunction() {
            return false;
        }
        //特权方法
        //因为这里作为闭包有权访问在构造函数定义的所有变量和函数
        //另外设置了this可以被调用
        this.publicMethod = function () {//外部只能通过这个方法访问私有属性和函数
            privateVariable++;
            return privateFunction();
        }
    }

第二种是:

    function Person(name) {
        //只有方法暴露,里面的属性是只能由方法访问
        //因为这些方法都是在构造函数内部定义的,他们作为闭包能够通过作用域链访问属性
        this.getName = function () {
            return name;
        };
        this.setName = function (value) {
            name = value;
        };
    }
    //每一个实例都不一样,每次new都会调用构造函数重新创建方法
    var person = new Person("nico");
    console.log(person.getName()); //返回nico
    person.setName("ggg");
    console.log(person.getName());//返回ggg

使用构造函数模式的缺点就是针对每个实例都会创建同样一组新方法

静态私有变量

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

  1. 这种方式

    • 私有变量和函数是由实例共享(因为被闭包引用着这个块级作用域)

    • 由于特权方法是在构造函数的原型上定义的,所以所有实例都使用同一个特权方法

(function () {
        //私有变量和私有函数
        var privateVariable = 10;

        function privateFunction() {
            return false;
        }

        //构造函数
        MyObject = function () { //没有函数声明,使用函数表达式创建
            //因为函数声明只能构建局部函数,现在创建了一个全局函数
            //因为这是全局函数,所以也相对来说就是一个静态了.
        };
        //公有/特权方法
        //在原型上定义方法,所有这个原型的实例都能共享
        //特权方法是一个闭包,保存着对包含作用域的引用(当前这个块级作用域)
        MyObject.prototype.publicMethod = function () { //这个是一个闭包
            privateVariable++;
            return privateFunction();
        }
    })();
  1. 这种方式,

    • 私有(静态)变量变成了由所有实例共享的属性(在当前这个块级作用域)

    • 构造函数能够静态变量,因为所有实例共享,所以每次调用构造函数的时候也会改变

    • 特权方法写在构造函数的原型上,所有实例共享

    (function () {
        //"静态"变量,对于每个实例来说都是共享的
        var name = "";

        //构造函数这里也是全局的,而且每次调用构造函数就会创建一个新的name值
        Person = function (value) {
            name = value;
        };
        //在原型上写方法,方法在实例之间共享
        Person.prototype.getName = function () {
            return name;
        };
        Person.prototype.setName = function (value) {
            name = value;
        };
    })();

    var person1 = new Person("mico");
    console.log(person1.getName());//返回mico
    person1.setName("oo");
    console.log(person1.getName());//返回oo

    //因为静态变量共享的关系,name被改了之后,全部实例的name都被改了
    var person2 = new Person("gg");
    console.log(person1.getName());//返回gg
    console.log(person2.getName());//返回gg

模块模式

  • 模块模式: 为单例创建私有变量和特权方法

  • 单例(singleton),指的是只有一个实例的对象

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

  • 模块模式创建的单例都是Object的实例,因为都是字面量创建,一般来说,单例都是全局对象,一般不会将他传递给一个函数,所以也没必要检验对象类型了

//单例用字面量方式创建
    var singleton = {
        name: value,
        method: function () {
            //方法代码
        }
    };
//模块模式
    var singleton = function () { //这是一个匿名函数
        //私有变量和私有函数
        var privateVariable = 10;

        function privateFunction() {
            return false;
        }

        //特权/公有方法和属性
        //返回的这个对象是在匿名函数内部定义的,因此它的公有方法有权访问私有变量和函数
        //从本质上来讲,定义的是单例的公共接口
        return { //返回了一个单例对象,{}是对象写法
            publicProperty: true, //只包含可以公开的属性和方法
            publicMethod: function () {
                privateVariable++;
                return privateFunction();
            }
        };
    }();

模块模式的应用例子

    //需要一个单例来管理应用程序级的信息
    //创建一个application对象(返回单例对象)
    var application = function () {
        //初始化一个数组
        var components = new Array();
        components.push(new BaseComponent());
        //返回的单例可以使用方法,然后获取数组的长度或者添加数组内容
        return {
            getComponentCount: function () {
                return components.length;
            },
            registerComponent: function (component) {
                if (typeof component == "object") {
                    components.push(component);
                }
            }
        }
    }();

增强的模块模式

改进的模块模式:在返回对象之前加入对其增强的代码.例如必须是某种类型的实例,同时还必须添加某些属性或方法对其加以增强

    var singleton = function () {
        //私有变量和私有函数
        var privateVariable = 10;

        function privateFunction() {
            return false;
        }

        //创建对象
        var object = new CustomType();

        //添加特权/公有属性和方法
        object.publicProperty = true;
        object.publicMethod = function () {
            privateVariable++;
            return privateFunction();
        };
        //返回这个对象
        return object;
    }();

线上猛如虎
2.2k 声望178 粉丝

你们都有梦想的,是吧.怀抱着梦想并且正朝着梦想努力的人,寻找着梦想的人,我想为这些人加油呐喊!