javascript经典面试题之for循环click
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
<script type="text/javascript">
function onMyLoad(){
var arr = document.getElementsByTagName("p");
for(var i = 0; i < arr.length; i++){
arr[i].onclick = function(){
alert(i);
}
}
}
</script>
</head>
<body onload="onMyLoad()">
<p>0</p>
<p>1</p>
<p>2</p>
<p>3</p>
<p>4</p>
</body>
</html>
该段代码期望实现效果如下:点击p标签,弹出该p标签位置序号。请问上述代码能否实现该需求,如果不能,应该如何实现?
原题分析
答案显而易见,不能。点击每个p标签都会弹出5。
要解决此问题,首先要了解闭包的概念。闭包是JavaScript语言的一个难点,也是它的特色,很多高级应用都要依靠它来实现。
变量作用域
JavaScript变量作用域的特点在于,函数内部可以读取该函数外部的变量,但函数外部无法读取该函数内部定义的变量,但是我们可以通过变通的方式获得。那就是在函数内部再定义一个函数:
function foo(){
var n=999;
function fee(){
alert(n); // 999
}
}
在上面的代码中,函数fee就被包括在函数foo内部,这时foo内部的所有局部变量,对fee都是可见的。但是反过来就不行,fee内部的局部变量,对foo就是不可见的。这就是Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
既然fee可以读取foo中的局部变量,那么只要把fee作为返回值,我们不就可以在foo外部读取它的内部变量了吗!
闭包的概念
简而言之,闭包就是能够读取其他函数内部变量的函数,由于在JavaScript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成定义在一个函数内部的函数。所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
闭包的用途
闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
function f1(){
var n=999;
nAdd=function(){
n+=1
}
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
在这段代码中,result实际上就是闭包f2函数,它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。
使用闭包的注意点
- 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
- 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
思考题
如果你能理解下面两段代码的运行结果,应该就算理解闭包的运行机制了。
代码一:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());
代码二:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()());
接下来回到文章开始的题目,解释一下为什么打印出来的数字都是5.
arr中的每一项onclick都是一个函数实例,这个函数也产生了一个闭包域,这个闭包域引用了外部闭包域的变量,即i,外部闭包域的私有变量内容发生变化,内部闭包域得到的值自然会发生改变(参照闭包的用途一节中的示例代码)
解决方案
方法一:增加若干个对应的闭包域空间(采用匿名函数实现)专门用来存储原先需要引用的内容(下标值),只限于基本类型(基本类型值传递,对象类型引用传递)
for(var i = 0; i<arr.length; i++){
(function (arg){//这个函数对象有一个本地私有变量arg(形参),该函数的function scope的closure对象属性有两个引用:arr和i。i的值随外部改变,但是本地的私有变量(形参)arg不会受影响,其值在一开始被调用时就决定了
arr[i].onclick = function () {//onclick函数实例的function scope的closure对象属性有一个引用arg
alert(arg);//只要外部空间的arg不变,这里的引用值就不会改变
}
})(i);//立即执行匿名函数,传递下标i(实参)
}
方法二:将下标作为对象属性(name:"i",value:i的值)添加到每个数组项(p对象)中
for(var i=0; i<arr.length; i++){
//为当前数组项(当前p对象)添加一个名为i的属性,值为循环体i变量的值
//此时当前p对象的i属性并不是对循环体的i变量的引用,而是一个独立p对象的属性,属性值在声明的时候就确定了
arr[i].i = i;
arr[i].onclick = function (){
alert(this.i);
}
}
方法三:增加若干个对应的闭包域空间用来存储下标。新增的匿名闭包空间内完成事件绑定。
//绑定的函数中的function scope中的closure对象的引用arg是指向将其返回的匿名函数的私有变量arg
for(var i = 0; i<arr.length; i++){
arr[i].onclick = (function(arg){
return function () {
alert(arg);
}
})(i);
}
方法四:使用ES6的let关键字
"use strict";
var arr = document.getElementsByTagName("p");
for(var i = 0; i<arr.length; i++){
let j = i;//块级变量
arr[i].onclick = function () {
alert(j);
}
}
请使用手机"扫一扫"x
javascript经典面试题之for循环click的更多相关文章
- 20道JavaScript经典面试题
该篇文章整理了一些前端经典面试题,附带详解,涉及到JavaScript多方面知识点,满满都是干货-建议收藏阅读 前言 如果这篇文章有帮助到你,️关注+点赞️鼓励一下作者,文章公众号首发,关注 前端南玖 ...
- JavaScript经典面试题(二)
前言: 近年来T行业就业者越来越多,有关于编程行业的高薪工作也变得越来越难找,竞争力越来越大,想要在众多的应聘者当中脱颖而出,面试题和笔试题一定要多加研究和琢磨,以下记录的是自己的面试过程之中遇到的一 ...
- 170106、用9种办法解决 JS 闭包经典面试题之 for 循环取 i
闭包 1.正确的说,应该是指一个闭包域,每当声明了一个函数,它就产生了一个闭包域(可以解释为每个函数都有自己的函数栈),每个闭包域(Function 对象)都有一个 function scope(不是 ...
- JavaScript经典面试题系列
1.javascript的typeof返回哪些数据类型 Object number function boolean underfind 2.例举3种强制类型转换和2种隐式类型转换? 强制(parse ...
- 用9种办法解决 JS 闭包经典面试题之 for 循环取 i
2017-01-06 Tomson JavaScript 转自 https://segmentfault.com/a/1190000003818163 闭包 1.正确的说,应该是指一个闭包域,每当声明 ...
- 前端必会的Javascript经典面试题
这是前端最基础的问题,也是在面试中难倒无数同学的经典问题 01. Javascript 数据类型 Javascript 数据类型 = 基本类型 + 引用类型 ES6 之前 5 + 1 = 6 种 ES ...
- 前端javaScript经典面试题
1.alert(1&&2),alert(1||0) alert(1&&2)的结果是2 只要“&&”前面是false,无论“&&”后面是t ...
- JavaScript经典作用域问题(转载)
题目 var a = 10; function test(){ a = 100; console.log(a); console.log(this.a); var a; console.log(a); ...
- JavaScript之经典面试题
1.作用域经典面试题 var num = 123; // f1函数写好了,作用域就定下来了,也就是作用域链定下来了 // f1函数作用域链: f1函数作用域 ==> 全局作用域 function ...
随机推荐
- Java虚拟机7:垃圾收集(GC)-2(并行和并发的区别)
1.并发编程下 这两个名词都是并发编程中的概念,在并发编程的模型下的定义: 并发:是在同一个cpu上同时(不是真正的同时,而是看来是同时,因为cpu要在多个程序间切换)运行多个程序. 并行:是多个或同 ...
- IDFA
https://zhuanlan.zhihu.com/p/24161646 IDFA全称是identifier for advertisers,在了解IDFA之前,我们需要了解另一个概念,叫UDID. ...
- centos 安装oracle 11g r2(三)-----表空间创建
centos 安装oracle 11g r2(三)-----表空间创建 创建表空间前要保证监听与数据库实例已经启动 1.启动监听 [oracle@localhost ~]$ lsnrctl start ...
- 机器学习与Tensorflow(3)—— 机器学习及MNIST数据集分类优化
一.二次代价函数 1. 形式: 其中,C为代价函数,X表示样本,Y表示实际值,a表示输出值,n为样本总数 2. 利用梯度下降法调整权值参数大小,推导过程如下图所示: 根据结果可得,权重w和偏置b的梯度 ...
- github或码云协同开发
协同开发 1.引子:假如三个人共同开发同一份代码,每个人都各自安排了任务,当每个人都完成了一半的时候,提交不提交呢? 要提交,提交到dev吗,都上传了一半,这样回家拿出来的代码根本跑不起来.所以, 为 ...
- POJ 2887
#include <iostream> #include <string> #define MAXN 2000 using namespace std; struct node ...
- 解决Navicat Premium终端操作mysql ONLY_FULL_GROUP_BY错误
解决navicate终端操作mysql ONLY_FULL_GROUP_BY错误 问题描述: [Err] 1055 - Expression #1 of SELECT list is not ...
- Java代码操作HDFS测试类
1.Java代码操作HDFS需要用到Jar包和Java类 Jar包: hadoop-common-2.6.0.jar和hadoop-hdfs-2.6.0.jar Java类: java.net.URL ...
- 数据库设计 Step by Step (1)——扬帆启航
引言:一直在从事数据库开发和设计工作,也看了一些书籍,算是略有心得.很久之前就想针 对关系数据库设计进行整理.总结,但因为种种原因迟迟没有动手,主要还是惰性使然.今天也算是痛下决心开始这项卓绝又令我兴 ...
- windows平台,实现录音功能详解
音频处理分为播放和录音两类.对这些处理,微软提供了一些列函数,称之为Waveform Functions.这篇文章讨论录音功能.会对微软提供的函数做简单说明,并对这些函数封装成c++类,再进一步封装成 ...