2025-02-06 日报 Day89

2025-02-06 日报 Day89

Yuyang 前端小白🥬

今日的鸡汤

成长是一个不断自我提升的过程,而学会自我负责,意味着我们终于有能力去正视现实并不断努力提升自己。

今日学习内容

1、JS 红皮书 P312-319 第十章:函数

今日笔记

1、this 对象: 在闭包中使用 this 会让代码变复杂。如果内部函数没有使用箭头函数定义,则 this 对象会在运行时绑定到执行函数的上下文。如果在全局函数中调用,则 this 在非严格模式下等于 window,在严格模式下等于 undefined。如果作为某个对象的方法调用,则 this 等于这个对象。匿名函数在这种情况下不会绑定到某个对象,这就意味着 this 会指向 window,除非在严格模式下 this 是 undefined。不过,由于闭包的写法所致,这个事实有时候没有那么容易看出来。来看下面的例子:

1
2
3
4
5
6
7
8
9
10
window.identity = "The Window";
let object = {
identity: "My Object",
getIdentityFunc() {
return function () {
return this.identity;
};
},
};
console.log(object.getIdentityFunc()()); // 'The Window'

前面介绍过,每个函数在被调用时都会自动创建两个特殊变量:this 和 arguments。内部函数永远不可能直接访问外部函数的这两个变量。但是,如果把 this 保存到闭包可以访问的另一个变量中,则是行得通的。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
window.identity = "The Window";
let object = {
identity: "My Object",
getIdentityFunc() {
let that = this;
return function () {
return that.identity;
};
},
};
console.log(object.getIdentityFunc()()); // 'My Object'

window.identity = "The Window";
let object = {
identity: "My Object",
getIdentity() {
return this.identity;
},
};

object.getIdentity(); // 'My Object'
object.getIdentity(); // 'My Object'
(object.getIdentity = object.getIdentity)(); // 'The Window'

2、内存泄露: 由于 IE 在 IE9 之前对 JScript 对象和 COM 对象使用了不同的垃圾回收机制(第 4 章讨论过),所以闭包在这些旧版本 IE 中可能会导致问题。在这些版本的 IE 中,把 HTML 元素保存在某个闭包的作用域中,就相当于宣布该元素不能被销毁。来看下面的例子:

1
2
3
4
function assignHandler() {
let element = document.getElementById("someElement");
element.onclick = () => console.log(element.id);
}

以上代码创建了一个闭包,即 element 元素的事件处理程序。而这个处理程序又创建了一个循环引用。匿名函数引用着 assignHandler()的活动对象,阻止了对 element 的引用计数归零。只要这个匿名函数存在,element 的引用计数就至少等于 1。也就是说,内存不会被回收。其实只要这个例子稍加修改,就可以避免这种情况,比如:

1
2
3
4
5
6
function assignHandler() {
let element = document.getElementById("someElement");
let id = element.id;
element.onclick = () => console.log(id);
element = null;
}

在这个修改后的版本中,闭包改为引用一个保存着 element.id 的变量 id,从而消除了循环引用。不过,光有这一步还不足以解决内存问题。因为闭包还是会引用包含函数的活动对象,而其中包含 element。即使闭包没有直接引用 element,包含函数的活动对象上还是保存着对它的引用。因此,必须再把 element 设置为 null。这样就解除了对这个 COM 对象的引用,其引用计数也会减少,从而确保其内存可以在适当的时候被回收。
3、立即调用函数表达式(IIFE,Immediately Invoked Function Expression):

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
(function () {
// 块级作用域
})();

// IIFE
(function () {
for (var i = 0; i < count; i++) {
console.log(i);
}
})();
console.log(i); // 抛出错误

// 内嵌块级作用域
{
let i;
for (i = 0; i < count; i++) {
console.log(i);
}
}
console.log(i); // 抛出错误

// 循环的块级作用域
for (let i = 0; i < count; i++) {
console.log(i);
}
console.log(i); // 抛出错误

let divs = document.querySelectorAll("div");
// 达不到目的!
for (var i = 0; i < divs.length; ++i) {
divs[i].addEventListener("click", function () {
console.log(i);
});
}

4、私有变量: JavaScript 没有私有成员的概念,所有对象属性都公有的。不过,倒是有私有变量的概念。任何定义在函数或块中的变量,都可以认为是私有的,因为在这个函数或块的外部无法访问其中的变量。私有变量包括函数参数、局部变量,以及函数内部定义的其他函数。来看下面的例子:

1
2
3
4
function add(num1, num2) {
let sum = num1 + num2;
return sum;
}

特权方法(privileged method)是能够访问函数私有变量(及私有函数)的公有方法。在对象上有两种方式创建特权方法。第一种是在构造函数中实现,比如:

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
function MyObject() {
// 私有变量和私有函数
let privateVariable = 10;
function privateFunction() {
return false;
}
// 特权方法
this.publicMethod = function () {
privateVariable++;
return privateFunction();
};
}

function Person(name) {
this.getName = function () {
return name;
};
this.setName = function (value) {
name = value;
};
}
let person = new Person("Nicholas");
console.log(person.getName()); // 'Nicholas'
person.setName("Greg");
console.log(person.getName()); // 'Greg'

5、静态私有变量: 特权方法可以通过私有作用域定义私有变量和函数来实现:

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
(function () {
// 私有变量和私有函数
let privateVariable = 10;

function privateFunction() {
return false;
}

// 构造函数
MyObject = function () {};

// 公有和特权方法
MyObject.prototype.publicMethod = function () {
privateVariable++;
return privateFunction();
};
})();

(function () {
let name = "";
Person = function (value) {
name = value;
};
Person.prototype.getName = function () {
return name;
};
Person.prototype.setName = function (value) {
name = value;
};
})();
let person1 = new Person("Nicholas");
console.log(person1.getName()); // 'Nicholas'
person1.setName("Matt");
console.log(person1.getName()); // 'Matt'
let person2 = new Person("Michael");
console.log(person1.getName()); // 'Michael'
console.log(person2.getName()); // 'Michael'

6、模块模式: Douglas Crockford 所说的模块模式,则在一个单例对象上实现了相同的隔离和封装。单例对象(singleton)就是只有一个实例的对象。按照惯例,JavaScript 是通过对象字面量来创建单例对象的,如下面的例子所示:

1
2
3
4
5
6
let singleton = {
name: value,
method() {
// 方法的代码
},
};

模块模式是在单例对象基础上加以扩展,使其通过作用域链来关联私有变量和特权方法。模块模式的样板代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let singleton = (function () {
// 私有变量和私有函数
let privateVariable = 10;
function privateFunction() {
return false;
}
// 特权/公有方法和属性
return {
publicProperty: true,
publicMethod() {
privateVariable++;
return privateFunction();
},
};
})();
此页目录
2025-02-06 日报 Day89