JavaScript面向对象和高级05

来源:互联网 发布:免费手机qq群发软件 编辑:程序博客网 时间:2024/06/05 11:45

闭包

1 理解闭包

1.1 闭包必备知识回顾

1.1.1 函数基础知识回顾

函数语法、函数参数
  • 1 函数是什么时候执行的 - 调用的时候才执行
  • 2 函数返回值可以包含什么类型
    • a 基本类型(number/string/boolean)
    • b 对象类型(object)
  • 3 怎么理解函数的返回值
函数内用来返回数据,相当于没有函数的时候直接使用该数据,即:function foo() {    var o = {age: 12};    return o;}var o1 = foo();// 相当于:var o2 = {age: 18};

1.1.2 作用域的结论

1 函数才会形成作用域2 JavaScript的作用域是词法作用域3 词法作用域:变量的作用范围 在代码写出来的就已经决定, 与运行时无关4 函数内部可以访问函数外部的变量(函数外部不能访问函数内部的变量)5 变量搜索原则:从当前链开始查找直到0级链,从高到低查找6 函数外部无法访问函数内部的变量当定义了一个函数,当前的作用域链就保存起来,并且成为函数的内部状态的一部分。

1.2 闭包的概念

闭包从字面上看就是封闭和包裹, 在函数中定义的变量在函数外部无法访问, 因此这个函数就构成闭包。> 闭包是一个受保护的变量空间。
  • 计算机科学中对闭包的解释
闭包是由 函数 以及 函数所处的环境 构成的综合体。或者闭包是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。函数会形成一个闭包包括:函数 以及 创建该函数的环境(作用域链)
  • 闭包代码抢先看
function foo() {    var num = 0;    return function() {        return num++;    };}var getNum = foo();console.log(getNum());

1.3 要解决闭包的什么问题(目标)

  • 想办法(在外部)访问到函数内部的数据

1.4 获取函数内部数据

函数内部的数据,在函数外面访问不到。函数对其内部的数据有一个保护的作用。

1.4.1 利用函数返回值

function foo() {    var num = 123;    return num;}var num1 = foo();
  • 疑问:获取两次数据是同一个数据吗? 不是同一个数据
var num1 = foo();var num2 = foo();console.log(num1 === num2); // true
  • 返回对象的情况
function foo() {    var obj = {num: 123};    return obj;}var o1 = foo();var o2 = foo();console.log(o1 === o2); // false

1.4.2 普通的函数返回值说明

两次调用函数,返回的数据并不是同一个数据。出现这个原因是:函数在每次调用的时候,函数内部的数据会被新创建一次要解决这个问题, 只需要保证, 函数 foo 只调用一次即可
  • 问题:函数只调用一次,但想获取多次数据怎么办?

2 闭包模型

function foo() {    var str = "BOSS";    return function() {        return str;    };}// 调用var f = foo();var str1 = f();var str2 = f();console.log(str1 === str2);
  • 闭包说明:
在函数(outer)内部定义的函数(inner),执行的时候可以访问到上一级作用域中的变量。因此,在函数(outer)外部,就可以间接访问到函数(outer)中的数据了。

2.1 函数内嵌函数

// 嵌套的函数function bar() {    var num = 123;    function f() {        console.log(num);    }    f();}bar(); // 123
  • 练习
// 问题:没有办法多次获得同一个数组function foo() {    var arr = [1, 3, 5, 7];    return arr;}// 在函数外面将arr中的所有数据遍历出来// 要求:遍历两次结果相同function func() {    var arr = ["a", "b", "c", "d"];    return function() {        return arr;    };}
  • 案例
// 利用闭包返回两个数的值function func() {    var n = Math.random();    var m = Math.random();    // ...}// 方式一:返回数组// 方式二:返回对象
  • 练习:
// 给 foo 提供两个方法,分别实现对 num 设置值和读取值function foo() {    var num;    return {        // 1        // 2    };}

2.2 闭包概念小结

闭包对内部的变量起到了保护的作用,除了返回的函数之外,无法通过任何其他手段访问到函数内部的数据

3 闭包的实际应用

  • 实现数据缓存
缓存:暂存数据方便后续计算中使用。缓存中存储的数据可以简单的认为是 键值对。工作中,缓存是经常被使用的手段。

计算机中的缓存

3.1 递归存在的问题

  • 存在大量的重复计算,使得执行效率很低。

  • 递归代码

// 计数var count = 0;// 递归function fib(n) {    count++;    if(n === 0 || n === 1) {        return 1;    }    return fib(n - 1) + fib(n - 2);}fib(20);console.log(count);

3.2 闭包实现缓存

  • 解决方式:缓存计算结果
即在计算的时候,1 首先查看缓存中有没有该数据,2 如果有,直接从缓存中取出来;3 如果没有,即递归,并将计算结果放到对应的缓存位置上。

3.2.1 抛开闭包谈问题

// 直接把 斐波那契数列 的前10项 放在数组中var fibsArr = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55];// 如何求第5项的值?第10项呢?
  • 没有闭包实现存在的问题:
1 数组放在全局环境会造成全局污染2 全局环境中谁都可以对数组修改3 缓存中完全信任数组(缓存),就会造成数据不准确的问题
  • 实现过程:
var fib = (function() {    // 缓存容器,放在闭包中,对数据起到保护作用    var arr = [];    return function(n) {        // 1 判断缓存容器中有没有        if(arr[n] !== undefined) {            // 1.1 取出结果            return arr[n];        } else {            // 缓存中没有            var res;            // 2 如果是0或者1直接 = 1            if (n === 0 || n === 1) {                // 保存计算结果                res = 1;            } else {                // 否则,递归调用,并保存计算结果                res = arguments.callee(n - 1) + arguments.callee(n - 2);            }            // 3 将递归调用的结果放入缓存中以便下次使用            arr[n] = res;            // 将结果返回            return res;        }    };})();

4 分析jQuery缓存实现

4.1 最基本的缓存结构

var cache = {};var cache = [];
  • 案例:实现缓存的设置和获取
// 设置var fn = function(k, v) {    cache[k] = v;};// 获取// 1 var cacheValue = cache[k];// 2 var getV = function(k) {    return cache[k];};

4.1.1 缓存注意事项

  • 1 缓存数量要在一定范围内,例如:100条
  • 2 缓存数据要可控,增删改查
  • 3 缓存需要被保护

4.2 实现数据缓存

var createCache = function() {    var internalCache = {};    var arr = [];    return function (k, v) {        if(v) {            if(!internalCache[k]) {                if(arr.length >= 50) {                    var deleteKey = arr.shift();                    delete internalCache[deleteKey];                }                arr.push(k);            }            internalCache[k] = v;        } else {            return internalCache[k];        }    };};
  • jQuery源码中缓存的实现
/** * Create key-value caches of limited size * 创建带有长度限制的键值对缓存 */function createCache() {    var keys = [];    function cache( key, value ) {        // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)        // 使用(key + " ") 是为了避免和原生(本地)的原型中的属性冲突        if ( keys.push( key + " " ) > Expr.cacheLength ) {            // Only keep the most recent entries            // 只保留最新存入的数据            delete cache[ keys.shift() ];        }        return (cache[ key + " " ] = value);    }    return cache;}// 就是用来设置缓存var typeCache = createCache();typeCache("cls", "stra");// 读取缓存typeCache["name"]typeCache["cls"];

5 沙箱模式

沙箱模式、沙盒模式、隔离模式沙箱(sandbox)介绍:用于为一些来源不可信、具备破坏力或无法判定程序意图的程序提供试验环境。然而,沙盒中的所有改动对操作系统不会造成任何损失。

5.1 JavaScript的沙箱模式

5.1.1 沙箱模式的作用

作用:隔离JavaScript中的作用域是:词法作用域。不存在 块级作用域,但是可以使用沙箱模式来模拟块级作用域:(function() {    var num = 123;})();var f = function() {    var num = 22;}f();// alert(num); // 函数外部无法访问num,这样就模拟了一个块级作用域
  • 在 JS 中讨论隔离,要隔离什么?变量
变量?代码逻辑?
  • 在JS中如何实现隔离?
考虑,JavaScritp中的作用域。只有函数能限定作用域,所以,只有函数才能实现隔离。

5.1.2 沙箱模式模型

// 沙箱模式 模型(function() {    // 代码    // 通过给 window 添加成员暴露沙箱提供的变量    // window.$ = window.jQuery = jQuery;})();// $()// jQuery()var num = (function() {    // return xxx;    })();
  • 问题:
1 为什么要是自调用函数?    要执行,不污染,隔离2 隔离的效果是什么?    沙箱内外 代码互不影响

5.1.3 沙箱模式应用

  • 练习:利用沙箱模式打印1-100的和
var count = 0;(function() {    // 应为js中没有块级作用域,所以在for循环中声明的变量 i    // 是一个全局变量    for(var i = 0; i <= 100; i++) {        count += i;    }})();alert(count);
  • 最佳实践:
在函数内定义变量的时候,将 变量定义 提到最前面。
  • 实际应用
(function(w) {    // 独立的环境    function it() {}    it.prototype.say = function() {};    // 其他操作代码    // ...    // 暴露到全局环境中    w.i$ = it;})(window);

5.1.4 沙箱模式的优势

将代码放到一个 立即执行的函数表达式(IIFE) 中,这样就能实现代码的隔离1 使用 立即执行的函数表达式 的好处就是:减少一个函数名称的污染,将全局变量污染降到最低2 代码在函数内部执行,形成了一个独立且外部无法访问的空间,这样就使得函数外部代码不会影响到函数内部的代码执行3 如果外部需要,则可以根据需求返回适当的数据。可以把window作为参数传入
原创粉丝点击