案例
写一个简单的给背景换色的案例来体现如何保存变量。
需求效果:有三个按钮,点击一个按钮,页面背景变为对应的颜色
使用两种方法来实现效果:
1、自定义属性的方式:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
button {
width: 300px;
height: 200px;
}
</style>
</head>
<body>
<button>红色</button>
<button>黄色</button>
<button>蓝色</button>
</body>
<script>
var ary = ["red", "yellow", "blue"];
var buttons = document.getElementsByTagName("button");
for (var i = 0; i < buttons.length; i++) {
buttons[i].myColor = ary[i];
buttons[i].onclick = function () {
document.body.style.backgroundColor = this.myColor;
}
}
</script>
</html>
2、闭包的方式:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
button {
width: 300px;
height: 200px;
}
</style>
</head>
<body>
<button>红色</button>
<button>黄色</button>
<button>蓝色</button>
</body>
<script>
var ary = ["red", "yellow", "blue"];
var buttons = document.getElementsByTagName("button");
for (var i = 0; i < buttons.length; i++) {
buttons[i].onclick = (function (i) {
return function () {
alert(ary[i]);
document.body.style.backgroundColor = ary[i];
}
})(i)
}
</script>
</html>
注意: 如果只是简单一个for循环,不加入任何可以保存变量的措施,最终for循环结束以后,全局变量 i 变为了3,导致无法实现对应的效果。
保存变量
之前提到过,目前学到的保存变量的措施有两种:一种是自定义属性的方式,一种是使用闭包的机制。
- 自定义属性:给元素对象设置自定义属性,让需要被保存的值保存在自定义属性中,相当于把这个值挂载到了对象上,当需要使用的时候,就使用对象成员访问即可调用这个值。
- 闭包:上面案例中,自执行函数里面return 返回了一个引用类型function,并且这个函数被外界的元素对象所接收,所以自执行函数执行形成的私有作用域不销毁,里面的私有变量 i 就被保存了下来。由于本案例中for循环执行了三次,相应的也就有三个未销毁的作用域,每个作用域中的私有变量i都被保存了下来。当外界button这个元素对象被点击时,触发其绑定的函数,函数中用到的变量不是私有变量则会去上级作用域查找,找到的是对应的自执行函数的未销毁的作用域中的私有变量i,也就可以实现案例的效果。
注: 这两种方法对比起来,闭包形成了三个不销毁的私有作用域,太消耗性能,而自定义属性的方式更节约性能,建议使用自定义属性的方式。
除了这两种保存变量的方法以外,还有一个使用ES6的方法来实现保存变量的目的。
let与块级作用域
ES6之前的作用域只有两种:
1、全局的
2、函数里面私有
有了ES6以后,就有一个块级作用域的概念。 当然,前提是使用ES6的语法,除了函数和对象的大括号,当遇到了 {} 就会形成一个块级作用域。
let 是ES6 的语法,当大括号中有 let 声明变量时(除了函数和对象的大括号),就会形成块级作用域了,比如遇到for循环的大括号,就会形成块级作用域。
因此上面的案例还可以这样写:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
button {
width: 300px;
height: 200px;
}
</style>
</head>
<body>
<button>红色</button>
<button>黄色</button>
<button>蓝色</button>
</body>
<script>
var ary = ["red", "yellow", "blue"];
var buttons = document.getElementsByTagName("button");
for (let i = 0; i < buttons.length; i++) {
buttons[i].onclick = function () {
alert(ary[i]);
document.body.style.backgroundColor = ary[i];
}
}
</script>
</html>
let 遇到 for 循环的大括号会形成块级作用域,块级作用域的作用与闭包类似,也可以保护和保存变量。for循环执行了三次代码,也就形成了三个块级作用域,当元素对象被点击时,也是可以通过查找机制,去块级作用域查找相应的变量i,也就实现了案例的效果。
事件委托实现这个案例(性能优化)
前面几种方法都比较消耗性能,相比较而言自定义属性相对比较节约性能一点,但也不是最优方案,还可以进行优化,那么使用节约性能的方法来实现这个效果该如何实现呢?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
button {
width: 300px;
height: 200px;
}
</style>
</head>
<body>
<button index="0">红色</button>
<button index="1">黄色</button>
<button index="2">蓝色</button>
</body>
<script>
var ary = ["red", "yellow", "blue"];
var buttons = document.getElementsByTagName("button");
document.body.onclick = function (ev) {
var target = ev.target;
// console.log(target);
if (target.tagName == "BUTTON") {
// console.log(target.getAttribute("index"));
var index = target.getAttribute("index");
document.body.style.backgroundColor = ary[index];
}
}
</script>
</html>
事件委托的方法,就是给元素的父级元素绑定事件,则当在其中的子代元素触发相应的事件时,也会触发父级元素绑定的事件,执行绑定的函数。