zoukankan      html  css  js  c++  java
  • 从头认识js-函数表达式

    定义函数的方式有两种:

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

    2.函数表达式(函数表达式与其他表达式一样,使用之前必须先声明)

    递归

    递归函数是一个函数通过名字调用自生的情况下构成的,递归函数必须要有结束条件。

    function factorial(num) {
        console.log('执行');
        if (num <= 1) {
            return 1;
        }
        return num * factorial(num - 1)
    }
    var fn = factorial;
    factorial = null;
    fn(5)

    上面我们定义了一个阶乘函数,我们把factorial函数保存在变量fn中,然后设置factorial为null,解除对函数的应用,用来模拟意外修改factorial的值,然后执行fn函数,就会出现报错,

    arguments.callee是一个指向正在执行函数的指针,所以上面函数修改如下:

    function factorial(num) {
        console.log('执行');
        if (num <= 1) {
            return 1;
        }
        return num * arguments.callee(num - 1)
    }
    var fn = factorial;
    factorial = null;
    fn(5)

    这样子我们就做到了防止意外情况修改factorial值而导致函数运行的错误,但是arguments.callee在严格模式下是不支持的,所以再次修改,使得递归函数在严格模式下也能支持上面函数的特定。代码如下:

    var factorial = (
        function f(num) {
            console.log('执行');
            if (num <= 1) {
                return 1;
            }
            return num * f(num - 1)
        }
    )
    var fn = factorial;
    factorial = null;
    console.log(fn(5));

    这样子一来,不管factorial的值怎么变,它在变化之前所指向的函数的功能是永远生效的,此时在外部作用域是访问不到f这个函数的,所以就无法破坏该函数的功能,这样子一来,在严格模式下,我们也能实现比较安全的递归函数。

    闭包

    闭包是指有权访问另一个函数作用中变量的函数。所以要构成必报必须要有两个条件:

    1.函数内部定义一个函数

    2.内部函数能够访问外部函数的活动对象(变量)

    下面是一个简单的闭包函数:

    function addNum(num) {
        return function () {
            return num + 1;
        }
    }
    var num = addNum(10);
    console.log(num);
    console.log(num())// 11
    num = null;
    console.log(num);

    函数addNum返回了一个函数,该函数使用了函数addNum的变量num,导致即使addNum执行完毕之后,返回的函数还是能够操作num。这样子就实现了变量常驻内存,但是很明显这样比较浪费内存,所以我们把外部函数num设置null,解除对返回的函数的引用,以此来释放内存。

    当某个哈数调用的时候,会创建一个执行环境及其相应的作用域链。然后使用arguments和其他命名参数的值初始化函数的活动对象。但在作用域链中,外部函数的活动对象始终处于第二外,外部函数的外部函数的活动对象处于第三位,......直至作为作用域链终点的全局执行环境。在函数执行过程中,为读取和写入变量的值,就需要在作用域链中查找变量。后台的每个执行环境都有一个表示变量的对象,函数这样子的局部环境的变量对象,则只在函数执行过程中存在,在创建外部函数addNum的时候,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中,当调用addSum函数的时候,会为函数创建一个执行环境,然后通过复制函数的[[Scope]]属性中的对象构建起执行环境的作用域链。此后,又有一个活动对象(在此作为变量对象使用)被创建并被推入执行环境作用域链的前端。显然,作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。

    无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量。一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。但是,闭包的情况又有所不同。在一个外部函数内部定义函数会将包含函数(即外部函数)的活动对象添加到它的作用链中,所以当外部函数执行完毕之后,它的活动对象也不会被销毁,因为内部函数的作用域链依然在引用这写活动对象,直到内部函数销毁之后,活动变量对象才会被销毁,从内存中清除。

    这个就是闭包实现的底层原理。

    内存泄漏

    看下面的代码

    function assginHandler() {
        var element = document.getElementById("ydb");
        element.onclick = function () {
            console.log(element.id);
        }
    }

    由于匿名函数保存了一个对assginHandler()的活动对象的引用,因此无法减少element的引用数。只要匿名函数存在,element的引用数至少为1,因此它所占用的内存空间就永远不会被回收。

    修改代码如下:

    function assginHandler() {
        var element = document.getElementById("ydb");
        var id = element.id;
        element.onclick = function () {
            console.log(id);
        }
        element = null;
    }

    在上面的代码中,通过把element.id的一个副本保存在一个变量中,并且在闭包中引用该变量消除了循环引用。但仅仅做到这一代呢是不够的,不要忘记了:闭包会引用包含函数(外部函数)的整个活动对象,而其中包含element。即使闭包不直接引用element,包含函数的活动对象也仍然会保存一个引用。因此,有必要把element变量设置为null。这样子就能够解除对DOM对象的引用,顺利地减少引用数,确保正常回收其占用的内存。

    模仿块级作用域

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

    function example() {
        for (var i = 0; i < 10; i++) {
            setTimeout(function () {
                console.log(i);// 10
            })
        }
      var i;
      console.log(i); } example();

    上面的输出语句,都会输出10,这个例子也证明了上述的观点,没有块作用域。在for语句执行完成之后,函数内部还是能访问i变量,匿名函数可以又怕没回来模仿块级作用域并避免这个问题:

    function example() {
        (function(){
            for (var i = 0; i < 10; i++) {
                console.log(i); // 正常输出
            }
        })()
        console.log(i);//Error i is not defined
    }
    example();

    这样子一来,函数内部就不能访问i变量,因为(function(){})()形成了一个块级作用域,作用域之外访问变量是访问不到的。

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

    私有变量

    任何在函数内部定义的变量,都可以认为是私有变量,因为不能在函数外部访问这些变量。私有变量包括函数的参数,局部变量和在函数内部定义的其他函数。

    看下面的例子:

    function Person(name) {
        this.getName = function () {
            return name;
        }
        this.setName = function (value) {
            name = value;
        }
    }
    var perosn1 = new Person('1');
    console.log(perosn1.getName());// 1
    perosn1.setName('2');
    console.log(perosn1.getName());// 2

    我们利用私有和特权方法,可以隐藏那些不应该直接修改的数据,必须通过特权方法去修改私有变量。

    不过在构造中定义特权方法有一个缺点,那就是你必须使用构造函数模式来达到这个目的。

    模块模式

    模块模式则是为单例创建私有的变量和特权方法。所谓单例,指的就是只有一个实例的对象。

    var application = function () {
        // 私有变量和函数
        var components = [];
        // 初始化
        components.push(new BaseComponent());
        // 公共方法
        return {
            getComponetnCount: function () {
                return components.length;
            },
            registerComponent: function (component) {
                if (typeof component == 'object') {
                    components.push(component);
                }
            }
        }
    }();

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

    增强模块模式

    在模块模式的基础上,在返回对象之前加入对其增强的代码。这种模式适合那些单例必须是某种类型的实例。

    var application = function () {
        // 私有变量和函数
        var components = [];
        // 初始化
        components.push(new BaseComponent());
        // 创建application的一个局部副本
        var app = new BaseComponent();
        // 公共方法
        app.getComponetnCount = function () {
            return components.length;
        },
            app.registerComponent = function (component) {
                if (typeof component == 'object') {
                    components.push(component);
                }
            }
        //返回这个副本
        return app;
    }();
  • 相关阅读:
    python基础:内置函数zip,map,filter
    python基础:网络编程
    python基础:异常捕捉
    jQuery demo
    day14 jQuery
    day13 JS Dom
    页面垂直方向出现两个滚动条问题?
    修复npm ERR! cb()never called!的错误;This is an error with npm itself. Please report this error at:
    vue——解决“You may use special comments to disable some warnings. Use // eslint-disable-next-line to ignore the next line. Use /* eslint-disable */ to ignore all warnings in a file. ” eslint报错,取消文件的rules
    原型链
  • 原文地址:https://www.cnblogs.com/jsydb/p/12244703.html
Copyright © 2011-2022 走看看