JavaScript学习记录1

1 背景

最近在学习JavaScript,今天学到了下面的内容,做个回顾吧。

作用域、作用域链精解、立即执行函数、闭包、闭包精细版

2 学习内容

2.1 作用域、作用域链精解基本概念

  1. 运行期上下文
    当函数执行时,会创建一个称为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,它所产生的执行上下文被销毁。
  2. 查找变量
    从作用域链的顶端依次向下查找
  3. [[scope]]
    每个JavaScript函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,这些属性仅供JavaScript引擎存取,[[scope]]就是其中一个。
    [[scope]]指的就是我们所说的作用域,其中存储了运行期上下文的集合。
  4. 作用域链
    [[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式链接,我们把这种链式链接叫作用域链。

2.1.1 示例

1
2
3
4
5
6
7
8
9
function a() {
function b() {
var b = 234;
}
var a = 123;
b();
}
var glob = 100;
a();

2.1.2 函数作用域链详解:

2.2 立即执行函数

2.2 功能:

此类函数没有声明,在一次执行过后即释放,适合做初始化工作。
实例如下

1
2
3
4
5
(function () {
var a = 1;
var b = 2;
console.log(a + b);
}())

1
2
3
(function (a , b , c) {
console.log(a + b + c * 2);
}(1 , 2 , 3))

1
2
3
4
5
var result = (function (a , b , c) {
var d = a + b * 2 + c * a;
return d
}(1 , 2 , 3))
console.log(result);

两种常用写法

(function() {} () ); W3C建议使用第一种
(function() {} ) ();

阿里巴巴曾经的一个考试题

1
2
3
function test(a , b , c , d) {
console.log( a + b + c + d);
}(1 , 2 , 3 , 4);

上面的代码就相当于下面的代码,讲函数test()的声明与(1 , 2 , 3 , 4);分开执行,但不报错也不输出任何的东西

1
2
3
4
function test(a , b , c , d) {
console.log( a + b + c + d);
}
(1 , 2 , 3 , 4);

2.3 闭包

2.3.1 基本概念

闭包:当函数内部被保存到外部时,将会生成闭包。闭包会导致原有作用域链不释放,造成内存泄漏。

1
2
3
4
5
6
7
8
9
10
11
12
13
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的引用

2.3.2 作用

2.3.2.1 实现共有变量(函数累加器)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function add() {
var count = 123;
function demo() {
count++;
console.log(count);
}
return demo;
}
var counter = add();
counter();
counter();
counter();
counter();
counter();
counter();

2.3.2.2 可以做缓存(存储结构)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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();

2.3.2.3 可以实现封装,属性私有化

2.3.2.4 模块化开发,防止污染全局变量

2.3.3 详解

经典代码

1
2
3
4
5
6
7
8
9
10
11
12
13
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中的代码并执行
要解决上述矛盾,必须使用立即执行函数,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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]();
}

2.3.4 基本规律

只有表达式才能被执行

1
function() {}

上面的代码称为函数声明,不是表达式,后面加()后会爆出低级语法错误

1
function() {} ();

而下面的代码会正常执行,相当于var demo = function ()
{console.log(“hello”);}是一个表达式,运行完一次之后就不能再运行了,相当于立即执行函数

1
2
3
var demo = function () {
console.log("hello");
}();

再函数声明前面加上 + - ! 后面加()都能将其变成函数表达式,也类似于立即执行函数,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
+ function test() {
console.log("hello");
}();

- function test() {
console.log("hello");
}();

! function test() {
console.log("hello");
}();

![](https://img-blog.csdnimg.cn/2019092214481018.png)

阿里曾经一道笔试题
使用原生js,addEventListener,给每个li元素绑定一个click事件,输出它们的顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!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>

2.3.5 扩展

写一个方法,求一个字符串的字节长度。(提示:字符串有一个方法charCodeAt() ,一个中文占两个字节,一个英文占一个字节)

定义和方法

charCodeAt()方法可返回指定位置字符的Unicode编码,这个返回值是0~65535之间的整数。(当返回值 <= 255时,为英文,当返回值 > 255时为中文)

语法

1
stringObject.charCodeAt(index)

eg:

1
2
3
4
<script>
var str = "Hello, World!";
document.write(str.charCodeAt(1)); //输出101
</script>

代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
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!我爱矿大");

简化代码

1
2
3
4
5
6
7
8
9
10
11
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!我爱矿大");

结果如下图

微店前端面试题

写出下面程序的执行结果(此题考查逗号运算符的结果,结果返回最后一个逗号后面的表达式的值)

1
2
3
4
5
6
7
8
9
var f = (
function f() {
return "1";
},
function g() {
return 2;
}
());
console.log(typeof f);

1
2
3
4
5
var x = 1;
if(function f() {}) {
x += typeof f;
}
console.log(x);


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达,可以邮件至 xingshuaikun@163.com。

×

喜欢就点赞,疼爱就打赏