作用域、作用域链精解、立即执行函数、闭包、闭包精细版
作用域、作用域链精解基本概念
- 运行期上下文
当函数执行时,会创建一个称为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,它所产生的执行上下文被销毁。 - 查找变量
从作用域链的顶端依次向下查找 - [[scope]]
每个JavaScript函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,这些属性仅供JavaScript引擎存取,[[scope]]就是其中一个。
[[scope]]指的就是我们所说的作用域,其中存储了运行期上下文的集合。 - 作用域链
[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式链接,我们把这种链式链接叫作用域链。
示例
function a() {
function b() {
var b = 234;
}
var a = 123;
b();
}
var glob = 100;
a();
函数作用域链详解:
立即执行函数
功能:
此类函数没有声明,在一次执行过后即释放,适合做初始化工作。
实例如下
(function () {
var a = 1;
var b = 2;
console.log(a + b);
}())
(function (a , b , c) {
console.log(a + b + c * 2);
}(1 , 2 , 3))
var result = (function (a , b , c) {
var d = a + b * 2 + c * a;
return d
}(1 , 2 , 3))
console.log(result);
两种常用写法
(function() {} () ); W3C建议使用第一种
(function() {} ) ();
阿里巴巴曾经的一个考试题
function test(a , b , c , d) {
console.log( a + b + c + d);
}(1 , 2 , 3 , 4);
上面的代码就相当于下面的代码,讲函数test()的声明与(1 , 2 , 3 , 4);分开执行,但不报错也不输出任何的东西
function test(a , b , c , d) {
console.log( a + b + c + d);
}
(1 , 2 , 3 , 4);
闭包
基本概念
闭包:
当函数内部被保存到外部时,将会生成闭包。闭包会导致原有作用域链不释放,造成内存泄漏。
function a() {
function b() {
var bb = 234;
aa++;
console.log(aa);
}
var aa = 123;
return b;
}
var glob = 100;
var demo = a();
demo();
demo();
代码执行期上下文过程
函数a返回的是b的引用
作用
-
实现共有变量(函数累加器)
function add() { var count = 123; function demo() { count++; console.log(count); } return demo; } var counter = add(); counter(); counter(); counter(); counter(); counter(); counter();
-
可以做缓存(存储结构)
function eater() { var food = ""; var obj = { eat : function () { console.log( "I am eating " + food); food = ""; }, push: function (myFood) { food = "myFood"; } } return obj; } var eater1 = eater(); eater1.push("banana"); eater1.eat();
-
可以实现封装,属性私有化
-
模块化开发,防止污染全局变量
详解
经典代码
function test() {
var arr = [];
for(var i = 0; i < 10; i++) {
arr[i] = function () {
document.write(i + " ");
}
}
return arr;
}
var myArr = test();
for(var j = 0; j < 10; j++) {
myArr[j]();
}
代码执行结果
代码执行过程,test()函数中的arr[i] = function() {}执行时并不执行function中的代码,只是传了一个引用值给arr[i],等到真正执行的时候才会回来看function中的代码并执行
要解决上述矛盾,必须使用立即执行函数,代码如下
function test() {
var arr = [];
for(var i = 0; i < 10; i++) {
(function (j) {
arr[j] = function () {
document.write(j + " ");
}
}(i));
}
return arr;
}
var myArr = test();
for(var j = 0; j < 10; j++) {
myArr[j]();
}
基本规律
只有表达式才能被执行
function() {}
上面的代码称为函数声明,不是表达式,后面加()后会爆出低级语法错误
function() {} ();
而下面的代码会正常执行,相当于var demo = function () {console.log(“hello”);}是一个表达式,运行完一次之后就不能再运行了,相当于立即执行函数
var demo = function () {
console.log("hello");
}();
再函数声明前面加上 + - ! 后面加()都能将其变成函数表达式,也类似于立即执行函数,代码如下
+ function test() {
console.log("hello");
}();
- function test() {
console.log("hello");
}();
! function test() {
console.log("hello");
}();
阿里曾经一道笔试题
使用原生js,addEventListener,给每个li元素绑定一个click事件,输出它们的顺序
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>demo</title>
<style>
*{
margin : 0;
padding : 0;
}
ul {
list-style: none;
}
li:nth-of-type(2n) {
background-color : red;
}
li:nth-of-type(2n + 1) {
background-color : green;
}
</style>
</head>
<body>
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
<li>forth</li>
</ul>
<script>
function test() {
var liCollection = document.getElementsByTagName('li');
for(var i = 0; i < liCollection.length; i++) {
(function (j) {
liCollection[j].onclick = function() {
console.log(j);
}
}(i))
}
}
test();
</script>
</body>
</html>
扩展
写一个方法,求一个字符串的字节长度。(提示:字符串有一个方法charCodeAt() ,一个中文占两个字节,一个英文占一个字节)
定义和方法
charCodeAt()方法可返回指定位置字符的Unicode编码,这个返回值是0~65535之间的整数。(当返回值 <= 255时,为英文,当返回值 > 255时为中文)
语法
stringObject.charCodeAt(index)
eg:
<script>
var str = "Hello, World!";
document.write(str.charCodeAt(1)); //输出101
</script>
代码如下所示:
function getBytesLen(str) {
var count = 0;
for(var i = 0; i < str.length; i ++) {
if(str.charCodeAt(i) <= 255) {
count ++;
} else if(str.charCodeAt(i) > 255) {
count += 2;
}
}
console.log(count);
}
getBytesLen("Hello, World!我爱矿大");
简化代码
function getBytesLen(str) {
var len = str.length;
var count = len;
for(var i = 0; i < len; i ++) {
if(str.charCodeAt(i) > 255) {
count ++;
}
}
console.log(count);
}
getBytesLen("Hello, World!我爱矿大");
结果如下图
微店前端面试题
写出下面程序的执行结果(此题考查逗号运算符的结果,结果返回最后一个逗号后面的表达式的值)
var f = (
function f() {
return "1";
},
function g() {
return 2;
}
());
console.log(typeof f);
var x = 1;
if(function f() {}) {
x += typeof f;
}
console.log(x);