这篇文章是用来记录由魏老师每天分享的技术问题,感谢珠峰教育和魏老师的支持复制代码
1.传统布局和flex布局有什么区别
(1) 性能上flex允许子元素不设置宽高,而是由算法动态去计算,性能会比设定好宽高的稍慢 但在这个时代大体没有影响 (2) 传统布局+flex布局!=所有布局,除了传统布局和flex布局外还有grid布局,多列布局等等n多种方法 (3) 传统布局,基于盒模型,依赖 display属性 、position属性 、float属性,而FLEX用来为盒状模型提供最大的灵活性,不强制要求子元素的大小,任何一个容器都可以指定为 Flex 布局,可以简便、完整、响应式地实现各种页面布局。题外扩展: (1)不要盒模型和布局弄混淆了,部局是DOM元素在文档中的位置排布,而模型指得是DOM元素的宽高大小的计算,模型一般由content-box,border-box,padding-box,-webkit-box等,默认为content-box (2)flex布局成为一个新的W3C标准规范 (3)flex在移动端兼容性:ios8不兼容,android4.4不兼容 (4)-webkit-box:在移动端开发中,所有的浏览器基本上都支持-webkit-box (5)文档流:文档流指元素在文档中的位置由元素在html里的位置决定,块级元素独占一行,自上而下排列;内联元素从左到右排列 (6)脱离文档流的方式: 浮动,通过设置float属性 绝对定位,通过设置position:absolute 固定定位,通过设置position:fixed (7)定位浮动知识:2.你对vue的源码了解吗,给我讲讲双向绑定原理怎么实现的,详细描述什么时候监听变化的,什么时候触发变化的?
(1) 一个vue对象实例化的过程中会生成一个observer对象,Observer通过Object.defineProperty里面的getter和setter实现数据的变化 (2)vue.js对模板做编译会解析生成一个个指令对象,每个指令对象都会关联一个watcher,通过wahcher监听数据变化触发setter方法题外扩展: (1) 能获取子组件内容但不能获取子组件的data里面的属性: this.nextTick (2)计算属性在模板中没有使用,那么能打印出来吗3.如何比较两个颜色的相似程度
(1) 首先将颜色拆分成r/g/b三个值,如果是字符串的颜色如#aabbff或者rgb(255,128,100)可以用正则表达式取出对应的r/g/b值。对于16进制字符串, 可以使用parseInt(‘0xaa’)转10进制整数。 (2) 然后对于两个颜色,可以使用距离 Math.sqrt( (r1-r2) (r1-r2) +(g1-g2)(g1-g2)+(b1-b2)*(b1-b2) )进行比较, 距离近则相似。 当然可以用Math.hypot( r1-r2, g1-t2, b1-b2) 来简化上述运算。4.一个单页面应用,有6张页面,F、E、A、B、C、D。 页面ABCD构成了一个冗长的用户验证过程。目前A、B、C对应用户验证过程的第1步,第2步,第3步。 页面F是首页,E是某张业务相关页面。用户到达页面E后, 系统发现用户没有认证,触发验证流程,到达页面A,然后开始A->B->C->D流程。 页面D是验证结果页面(验证成功页面)。 请问,如果到达页面D后, 如何让用户点击返回可以返回页面F,而忽略中间流程(注:用户可能根本没有到达过F,比如微信分享直接进入了E)
这个问题初一看是对单页面路由架构的考察。也是一个很好的引入问题,可以考察非常多方面。 比如说:如何实现页面切换动画? A、B、C都是表单的话,如何缓存用户输入完成的表单数据?……回到问题,因为history api提供了push/pop/replace三种操作,无论是其中的任何一种都无法实现上述的效果。 一个路由系统,首先要监听浏览器地址的变化,然后根据变化渲染不同的页面。- 在页面到达D后,关闭对路由变化页面渲染的监听。
- 路由系统要进行多次POP,可以用history.go(-n)实现
- 路由栈清空到只剩下一张页面时,将这张页面替换为F。
- PUSH一张页面D。 如果在HTML上有一个类似「轮播图」的设计,就是每一张页面是一张轮播图,将轮播图设置成只有「F」和「D」。
- 恢复路由监听。 这个问题的另一个考点是,在上述完整的计算过程当中,需要知道当前历史记录中的页面数,页面数可以通过localStorage实现,在ls中维护一个变量,每次push的时候+1,并写入history.state。 POP的时候读取history.state将变量重置。
5.一个无序数组中,怎么找到和最大的子序列?
(1)最简单也最暴力的解法:首先列出 所有的子序列,然后找出其中和最大的 即可; 实现思路:一个 记录当前最大值的变量maxSum;一个 子序列开始和结束的游标 变量;一个 当前子序列的和 的暂存变量,我们称之为 currentSum 或者 tmpSum(下文中 使用currentSum) 找到所有的 子序列 我们可以通过两层循环的方式来解决第一层循环 i 从 0~ length-1; 第二层循环 j 从 i ~ length - 1; 这样的循环里 就可以找到所有的子序列了 下一步 我们是要计算出所有子序列的和 最简单的办法 就是 第三层循环从 i ~ j 累加求出和 然后求出来的每个和 和 maxSum 去比较,如果比maxSum 大 就替换伪代码:maxSum = maxSum < currentSum ? currentSum : maxSum; 三层循环结束后 maxSum就是我们要 求的解 return maxSum即可 这个算法的时间复杂度是O(n^3);2)简化解法:我们在第二层循环中,我们已经知道 当前的 i/j之前的方法是在第三层的循环中 计算 i ~ j 的和 现在 我们在第二层中 在进入第二层之前 我们重置一下currentSum 第一次循环 是 i ~ i 当前我们就把 i 的值 记录到 currentSum去跟 maxSum 对比 然后 maxSum = maxSum < currentSum ? currentSum : maxSum; 第二次循环 是 i ~ i + 1 我们就把 当前的 i+1 累加到currentSum 这时候的 currentSum就是 i ~ i+1 的值,再去跟maxSum去比 然后 maxSum = maxSum < currentSum ? currentSum : maxSum; 以此类推 第二层的循环中 就可以 计算出 以当前 i 开头的子序列中 最大的子序列是多少 现在我们看回 到 第一层循环 i 的取值 是从 0 ~ length-1 那么我们是不是 可以找到 i 从 0 ~ length-1 所有的子序列中和最大的伪代码思路: 第一层 i (0 ~ length-1) currentSum 清零 第二层 j(i ~ length-1) currentSum 累加 maxSum = maxSum < currentSum ? currentSum : maxSum; return maxSum; 算法的时间复杂度是 O(n^2)3)demo数组:[-2, 1, -3, 4, -1, 2 , 1, -5, 4] 首先 我们可以简单的简化一下 这个数组 把 相邻 的 同 正负的数字合起来,因为同符号的连续数 一定会同时存在在最大子序列里 比如 [-1, -2, -3, 1, 2, 13] 那跟 [-6, 16] 是没有区别的 [-2, 1, -3, 4, -1, 2, 1, -5, 4] ==> [-2, 1, -3, 4, -1, 3, -5, 4] 然后 我们从头开始看 -2 这是第一个元素 那么 我们认为 当前的 最大子序列和 就是 -2 然后 发现了一个正数 1 那我们可以确定 -2 一定不包含在 我们的最大子序列中 也就是说 数组开头 如果是负数 可以忽略过去 现在 我们的数组 变成了 [1, -3, 4, -1, 3, -5, 4] 同理 结尾的如果是 负数 也不需要考虑 现在我们的数组 变成了 [1, -3, 4, -1, 3, -5, 4] 我们继续,现在 第一个元素是 1 最大和 是1 然后下一个数是 -3 那么 -3 对 1 这个数 起到了阻断作用 也就是说 -3 把 前边所有正数 积累的能量都磨平了 甚至还变成了一个负数 那么 -3 我们称之为 一个阻断 当前的 最大和 还是 1 现在 我们到了 4 那么现在的最大值 就是4 我们继下个数字是 -1 之前最大的和是 4 加起来之后是 3 影响并不大 我们继续带着他 向后看 下一个 是个正数 3 也就是 4 -1 3 这样的情况 我们是不是可以认为 这个 -1 虽然降低了 和 但是 他连接了左右的正数 让我们当前的最大值 变成了 6 更新最大值 继续看 下一个是-5 同理 之前的 6+ -5 和 还是1 也没有阻断 我们去看看 后边有没有一个大数 拯救我们 后边 一个数 是 4 加上我们刚才记录的 1 和是5 最后还是没有挑战成功 所以 最大的和 还是之前的 6公式:nums是我们的源数组 nums[i] 就是我们的当前元素 currentMax[i] 记录 我们以 i 结尾的子序列里 最大的一个子序列 那么 currentMax[i] = max(currentMax[i - 1] + nums[i], nums[i]) 这个公式被称之为 状态转移公式 我们的这种解法 称之为 动态规划解法 简称:PD 然后我们去遍历 currentMax 这个数组 里边的最大值 就是我们要找的 最大值伪代码: var maxSubArray = function(nums) { // 初始化源数组,初始化An为结束的最大值 let A = nums; let dp = []; let maxSum = A[0]; dp[0] = A[0]; for(let i = 1; i < A.length; i++) { //状态转移公式 dp[i] = max(A[i], dp[i-1] + A[i]) maxSum = dp[i] > maxSum ? dp[i] : maxSum; } return maxSum;}function max(a, b) { return a > b ? a : b; }复制代码
6. 请你说说函数防抖和函数节流的应用场景和原理?
函数节流场景: (1)例如:实现一个原生的拖拽功能(如果不用H5 Drag和Drop API),我们就需要一路监听mousemove事件,在回调中获取元素当前位置,然后重置dom的位置。不加以控制,每移动一定像素而出发的回调数量是会非常惊人的,回调中又伴随着DOM操作,继而引发浏览器的重排和重绘,性能差的浏览器可能会直接假死。 (2)这时,我们就需要降低触发回调的频率,比如让它500ms触发一次或者200ms,甚至100ms,这个阀值不能太大,太大了拖拽就会失真,也不能太小,太小了低版本浏览器可能会假死,这时的解决方案就是函数节流【throttle】。 (3)函数节流的核心就是:让一个函数不要执行得太频繁,减少一些过快的调用来节流。函数去抖场景: (1)对于浏览器窗口,每做一次resize操作,发送一个请求,很显然,我们需要监听resize事件,但是和mousemove一样,每缩小(或者放大)一次浏览器,实际上会触发N多次的resize事件,这时的解决方案就是节流【debounce】。 (2)函数去抖的核心就是:在一定时间段的连续函数调用,只让其执行一次*整体函数总结一下: (1)对于按钮防点击来说的实现:一旦我开始一个定时器,只要我定时器还在,不管你怎么点击都不会执行回调函数。一旦定时器结束并设置为null,就可以再次点击了。 (2)对于延时执行函数来说的实现:每次调用防抖动函数都会判断本次调用和之前的时间间隔,如果小于需要的时间间隔,就会重新创建一个定时器,并且定时器的延时为设定时间减去之前的时间间隔。一旦时间到了,就会执行相应的回调函数。 节流 防抖动和节流本质是不一样的。防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行。- underscore 节流函数,返回函数连续调用时,func 执行频率限定为 次 / wait
- @param {function} func 回调函数
- @param {number} wait 表示时间窗口的间隔
- @param {object} options 如果想忽略开始函数的的调用,传入{leading: false}。
- 如果想忽略结尾函数的调用,传入{trailing: false}
- 两者不能共存,否则函数不能执行
- @return {function} 返回客户调用函数
_.throttle = function(func, wait, options) { var context, args, result; var timeout = null; // 之前的时间戳 var previous = 0; // 如果 options 没传则设为空对象 if (!options) options = {}; // 定时器回调函数 var later = function() { // 如果设置了 leading,就将 previous 设为 0 // 用于下面函数的第一个 if 判断 previous = options.leading === false ? 0 : _.now(); // 置空一是为了防止内存泄漏,二是为了下面的定时器判断 timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; return function() { // 获得当前时间戳 var now = _.now(); // 首次进入前者肯定为 true // 如果需要第一次不执行函数 // 就将上次时间戳设为当前的 // 这样在接下来计算 remaining 的值时会大于0 if (!previous && options.leading === false) previous = now; // 计算剩余时间 var remaining = wait - (now - previous); context = this; args = arguments; // 如果当前调用已经大于上次调用时间 + wait // 或者用户手动调了时间 // 如果设置了 trailing,只会进入这个条件 // 如果没有设置 leading,那么第一次会进入这个条件 // 还有一点,你可能会觉得开启了定时器那么应该不会进入这个 if 条件了 // 其实还是会进入的,因为定时器的延时 // 并不是准确的时间,很可能你设置了2秒 // 但是他需要2.2秒才触发,这时候就会进入这个条件 if (remaining <= 0 || remaining > wait) { // 如果存在定时器就清理掉否则会调用二次回调 if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { // 判断是否设置了定时器和 trailing // 没有的话就开启一个定时器 // 并且不能不能同时设置 leading 和 trailing timeout = setTimeout(later, remaining); } return result; }; };复制代码
7.电商网站A和电影票网站B合作,A的用户,可以通过A网站下单购买电影票,之后跳转跳转到B(不需要登录)去选座位,如果A、B是同域名,比如,,b.d,om,b.domain.com能不能共享cookie?
如果不同域如何处理?这其实是个单点登录的问题,同一级域名下设置cookie设在一级域名下, 同级域名下的cookie皆共享缺点: 1,同一域名限制 2,登录的规则一致,如果不同域的话就是跨域问题,跨域问题可以用jsonp解决 3、二者的区别i、作用域
相同浏览器的不同页面间可以共享相同的 localStorage(页面属于相同域名和端口),但是sessionStorage只能在同源(相同域名相同窗口)同窗口访问,但是当sessionStorage在同一窗口下转到同源页面还是可以访问的,因为这时候还是同源同窗口,不要单纯理解为两个不同的页面之间不能访问相同sessionStorage。比如你在A网页设置了一个sessionStorage的值,然后你同时在新的窗口下打开B网页,这时候你尝试在B网页得到A网页设置的sessionStorage是不可以的,但是当你在A网页跳转到B网页的时候,这时候你会发现B网页可以访问A网页中的sessionStorage。所以sessionStorage针对的是同源同窗口,不是同源同页面。
ii、生命周期
localStorage生命周期是永久,这意味着除非用户自己清除localStorage信息或者用户清除浏览器缓存,否则这些信息将永远存在。 sessionStorage生命周期为当前窗口或标签页,一旦窗口或标签页被永久关闭了,那么所有通过sessionStorage存储的数据也就被清空了 总结:
cookie与sessionStorage、localStorage的区别
1、cookie可以在浏览器端与服务器端之间通信,是服务器端获取用户信息、保持一种持久客户端状态的关键。而sessionStorage、localStorage
虽然也可以保存会话数据,但是不能与服务器端进行信息交换。
2、cookie的容量比较小。而sessionStorage、localStorage有较大的容量
3、试用的浏览器范围不同。由于sessionStorage与localStorage是HTML5标准推出的,所以在IE7以及IE7以下的版本不适用。替代方案是采用IE的userData.(有兴趣可以了解一下)
三者的异同
特性 Cookie localStorage sessionStorage 数据的生命期 一般由服务器生成,可设置失效时间。如果在浏览器端生成Cookie,默认是关闭浏览器后失效 除非被清除,否则永久保存 仅在当前会话下有效,关闭页面或浏览器后被清除 存放数据大小 4K左右 一般为5MB 与服务器端通信 每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题 仅在客户端(即浏览器)中保存,不参与和服务器的通信 易用性 需要程序员自己封装,源生的Cookie接口不友好 源生接口可以接受,亦可再次封装来对Object和Array有更好的支持
8. 说说什么是xss攻击?如何攻击,如何防御?
(1)XSS 跨网站指令码(英语:Cross-site scripting,通常简称为:XSS)是一种网站应用程式的安全漏洞攻击,是代码注入的一种。它允许恶意使用者将程式码注入到网页上,其他使用者在观看网页时就会受到影响。这类攻击通常包含了 HTML 以及使用者端脚本语言。 (2)XSS 分为三种:反射型,存储型和 DOM-based (3)如何攻击: XSS 通过修改 HTML 节点或者执行 JS 代码来攻击网站。 例如通过 URL 获取某些参数 上述 URL 输入可能会将 HTML 改为,这样页面中就凭空多了一段可执行脚本。这种攻击类型是反射型攻击,也可以说是 DOM-based 攻击。 也有另一种场景,比如写了一篇包含攻击代码 的文章,那么可能浏览文章的用户都会被攻击到。这种攻击类型是存储型攻击,也可以说是 DOM-based 攻击,并且这种攻击打击面更广。 如何防御 最普遍的做法是转义输入输出的内容,对于引号,尖括号,斜杠进行转义 这里写图片描述 内容安全策略 (CSP) 是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本 (XSS) 和数据注入攻击等。无论是数据盗取、网站内容污染还是散发恶意软件,这些攻击都是主要的手段。 我们可以通过 CSP 来尽量减少 XSS 攻击。CSP 本质上也是建立白名单,规定了浏览器只能够执行特定来源的代码。 通常可以通过 HTTP Header 中的 Content-Security-Policy 来开启 CSP 只允许加载本站资源,只允许加载 HTTPS 协议图片,允许加载任何来源框架 这里写图片描述 这里写图片描述 这里写图片描述 参考网站
9. CSRF攻击是什么?如何防范?
CSRF概念:CSRF跨站点请求伪造(Cross—Site Request Forgery),跟XSS攻击一样,存在巨大的危害性,你可以这样来理解: 参考网站10. 如果发现在某个用户的电脑上,网站的静态资源打不开了,如何确定是CDN的问题还是那个用户机器、浏览器的问题?
1、自己的电脑访问cdn地址,排除是否cdn问题; 2、让用户换浏览器再试,排除用户浏览器的问题(禁用所有插件、清除缓存); 3、已经排除cdn和用户浏览器的问题,那就是用户的机器(或者所在的网络)有问题。有可能是用户所在的公司网络禁止下载某些资源。 4、推荐一个本地网络诊断的工具的工具https://cdn.dns-detect.alicdn.com/https/doc.html 可以检 可以检查dns和本地ip地址是否正常11. 请说说在hybrid端实现类似原生般流畅的体验,要注意哪些事项?
1.资源加载,采用预加载,优先加载到内存中,做到无缝切换,使用原生loading 2.离线加载静态资源,不走网络请求 3.尽量做到局部更新,动画使用transform,will-change来提高性能 4.使用webkit over scrolling加速滚动条的滚动,去掉user selection,去掉高亮等,手势交互原生实现,复杂交互如日期选择器等调用原生组件 5.遵循APP UI设计规范 6.考虑文件diff更新,或主动后台请示,在某个时刻跟新,两个版本直接的兼容问题 7.APP方法加上安全性的考虑12.事件触发的三个阶段
document 往事件触发处传播,遇到注册的捕获事件会触发 传播到事件触发处时触发注册的事件 从事件触发处往 document 传播,遇到注册的冒泡事件会触发 事件触发一般来说会按照上面的顺序进行,但是也有特例,如果给一个目标节点同时注册冒泡和捕获事件,事件触发会按照注册的顺序执行。// 以下会先打印冒泡然后是捕获node.addEventListener('click',(event) =>{ console.log('冒泡')},false);node.addEventListener('click',(event) =>{ console.log('捕获 ')},true)复制代码
注册事件
通常我们使用 addEventListener 注册事件,该函数的第三个参数可以是布尔值,也可以是对象。对于布尔值 useCapture 参数来说,该参数默认值为 false 。useCapture 决定了注册的事件是捕获事件还是冒泡事件。对于对象参数来说,可以使用以下几个属性 capture,布尔值,和 useCapture 作用一样 once,布尔值,值为 true 表示该回调只会调用一次,调用后会移除监听 passive,布尔值,表示永远不会调用 preventDefault一般来说,我们只希望事件只触发在目标上,这时候可以使用 stopPropagation 来阻止事件的进一步传播。通常我们认为 stopPropagation 是用来阻止事件冒泡的,其实该函数也可以阻止捕获事件。stopImmediatePropagation 同样也能实现阻止事件,但是还能阻止该事件目标执行别的注册事件。node.addEventListener('click',(event) =>{ event.stopImmediatePropagation() console.log('冒泡')},false);// 点击 node 只会执行上面的函数,该函数不会执行node.addEventListener('click',(event) => { console.log('捕获 ')},true)复制代码
事件代理 如果一个节点中的子节点是动态生成的,那么子节点需要注册事件的话应该注册在父节点上
- 1
- 2
- 3
- 4
- 5
事件代理的方式相对于直接给目标注册事件来说,有以下优点 节省内存 不需要给子节点注销事件
12.问题如下:
- 什么是重绘(Repaint)和回流(Reflow)?
- 哪些动作可能会导致重绘(Repaint)和回流(Reflow)的发生?
- 重绘(Repaint)和回流(Reflow)和Event loop的关系?
- 如何减少重绘(Repaint)和回流(Reflow)?
13. 2*8最有效率的计算方法
2<<3 讲一个数左移动n位就相当于乘以2的n次方,一个数要乘以8只需要将他左移动三位就行了,而位运算cpu直接支持,所以运算效率最高,所以2乘以8最有效的是 2<<3 例如11 >> 2,则是将数字11右移2位 右移一位相当于除2,右移n位相当于除以2的n次方。14.发布订阅模式和观察者模式有什么区别其实订阅模式有一个调度中心,对订阅事件进行统一管理。而观察者模式可以随意注册事件,调用事件,虽然实现原理都雷同,设计模式上有一定的差别,实际代码运用中差别在于:订阅模式中,可以抽离出调度中心单独成一个文件,可以对一系列的订阅事件进行统一管理。复制代码
15.apply和call方法有什么区别
区别: call, apply方法区别是,从第二个参数起,call方法参数将依次传递给借用的方法作参数而apply直接将这些参数放到一个数组中再传递, 最后借用方法的参数列表是一样的.应用场景: 当参数明确时可用call, 当参数不明确时可用apply给合arguments复制代码
16.js对象和hashMap
数组:也叫线性连续表怎么将一组值相差范围巨大,个数波动范围很大的下标放入特定的数组空间呢?用哈希算法、也叫散列算法哈希算法的目的就是将不定的输入转换成特定范围的输出,并且要求输出尽量均匀分布1,除法散列法最直观的一种,小茄上文使用的就是这种散列法,公式:index = key % 162,平方散列法求index是非常频繁的操作,而乘法的运算要比除法来得省时(对现在的CPU来说,估计我们感觉不出来),所以我们考虑把除法换成乘法和一个位移操作。公式:index = (key * key) >> 283,斐波那契(Fibonacci)散列法复制代码
17.vue组件中data必须是一个函数的原因
vue组件中data值不能为对象,因为对象是引用类型,组件可能会被多个实例同时引用。如果data值为对象,将导致多个实例共享一个对象,其中一个组件改变data属性值,其它实例也会受到影响。 上面解释了data不能为对象的原因,这里我们简单说下data为函数的原因。data为函数,通过return 返回对象的拷贝,致使每个实例都有自己独立的对象,实例之间可以互不影响的改变data属性值。复制代码
18.原型链
实例的__protpo__指向的是原型对象。 实例的构造函数的prototype也是指向的原型对象。 原型对象的construor指向的是构造函数。 对象的__proto__它的是原型,而原型也是一个对象,也有__proto__属性,原型的 __proto__又是原型的原型,就这样可以一直通过__proto__想上找,这就是原型链,当向上 找找到Object的原型的时候,这条原型链就算到头了复制代码
19.闭包
通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。但是,在创建了一个闭包以后,这个函数的作用域就会一直保存到闭包不存在为止function makeAdder(x) { return function(y) { return x + y; }; } 闭包创建的第二种形式:传入一个全局变量创建闭包的常见方式,就是在一个函数内部创建另一个函数。//通过闭包可以获得函数fn的局部变量userfunction fn(){ var user='zhang'; return function(){ return user; }//通过匿名函数返回局部变量user}console.log(fn()());//zhang 通过box()()来直接调用匿名函数返回值var b=fn();console.log(b());//zhang 另一种调用匿名函数返回值通过闭包可以实现函数内的局部变量的累加:function fn(){ var num=100; return function(){ num++; return num; }}var b=fn();//获得函数//你会发现局部变量num并没有在内存当中消失console.log(b());//调用匿名函数console.log(b());//第二次调用匿名函数,实现累加由于在闭包所在的作用域返回的局部变量不会被销毁,所以会占用内存。过度的使用闭包会迫使性能下降,因此建议大家在有必要的情况下再使用闭包。作用域链的机制会导致一个问题,在循环中里的匿名函数取得的任何变量都是最后一个值function fn(){ var arr=[]; //i为fn函数中的局部变量。 for(var i=0;i<3;i++){ arr.push(function(){ return i; }); } return arr;}var b=fn();for(var i=0;i