介绍一些重要的JavaScript知识点在框架中的应用, 不完整, 之后再学习到会继续更新.

默认值

1
2
3
4
5
6
//lib1.js
var libraryName = 'Lib1';
//lib2.js
var libraryName = 'Lib2';
//app.js
console.log(libraryName);
1
2
3
4
5
<body>
<script src="lib1.js"></script>
<script src="lib2.js"></script>
<script src="app.js"></script>
</body>

尽管代码分别在三个文件中, 但实际上都处于全局作用域, 等同于:

1
2
3
4
5
6
7
<body>
<script>
var libraryName = 'Lib1';
var libraryName = 'Lib2';
console.log(libraryName);
</script>
</body>

可以将lib2.js中的代码改为:

window.libraryName = window.libraryName || 'Lib2';

如果全局作用域已经存在libraryName这个变量且有值, 那么libraryName的值不会变化, 否则就会被赋值为'Lib2'

在框架中运用这个方法来确定变量名是否在之前已经被使用, 防止其被改变. ES6中引入了const, 也可以解决这个问题, 利用const声明变量, 变量就不可被改变.

创建命名空间

命名空间: 变量和函数的container, 分离具有相同变量名的变量和函数名的函数.

JavaScript中没有命名空间的概念, 因为在JavaScript中, 可以利用对象实现命名空间的功能(faking namespaces).

1
2
3
4
5
6
7
8
9
10
var greet = 'Hello';
var greet = 'Hola';

console.log(greet);

var english = {};
var spanish = {};

english.greet = 'Hello';
spanish.greet = 'Hola';

不可以直接定义属性的属性:

1
2
3
var english = {};
english.greetings.greet = 'Hello';
// Error

因为点操作符的优先级高于赋值操作符, 关联性是从左到右, 因此会先执行english.greetings, 结果为undefined, 所以会抛出错误. 应该这样做:

1
2
3
var english = {};
english.greetings = {};
english.greetings.greet = 'Hello';

或者:

1
2
3
4
5
6
7
var english = {
greetings: {
basic: 'Hi'
}
};

english.greetings.greet = 'Hello';

参考

函数重载

在Java或C++中有函数重载(function overloading)的概念,即可以定义多个同名函数,但接受不同的参数, 而JavaScript语言中没有这个概念, 因为函数是对象, 所以无法定义多个同名函数, 但因为在JavaScript中函数是一等公民(first class function),可以通过以下方法实现类似的函数重载的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function greet(firstname, lastname, language){
if (language === 'en'){
console.log('Hello ' + firstname + ' ' + lastname);
}
if (language === 'es'){
console.log('Hola ' + firstname + ' ' + lastname);
}
}

greet('John', 'Doe', 'en');
greet('John', 'Doe', 'es');

function greetEnglish(firstname, lastname){
greet(firstname, lastname, 'en');
}

function greetSpanish(firstname, lastname){
greet(firstname, lastname, 'es');
}

greetEnglish('John','Doe');
greetSpanish('John','Doe');

Whitespaces

Whitespaces包括回车,制表符,空格.

JavaScript is very liberal about whitespaces, 即使是这样用⬇️, 也完全合理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var
//first name of the person
firstname,

//last name of the person
lastname,

// the language
language;

var person = {
// the first name
firstname: 'John',

// the last name
// (always required)
lastname: 'Doe'
}

console.log(person);

在许多框架的源代码中这样的格式十分常见,开发者利用JavaScript引擎对待whitespace的特性将评注性文字以更加可读的形式呈现.

立即调用函数表达式(IIFE)

函数声明不可匿名:

1
2
3
4
function (name){
console.log('Hello ' + name);
};
//SyntaxError: unknown: Unexpected token

上面这段代码会抛出错误, 因为语法分析器(Syntax Parser)看到关键词function之后会认为这是个函数声明, 但函数声明必须有名字,所以会抛出错误. 为了避免这个错误,在function前加上括号, 语法分析器就不会将其解析成函数声明. 括号在JS中是操作符, JS引擎将在括号内的代码都看作表达式.

1
2
3
4
// 加上括号之后相当于创建了一个函数对象
(function (name){
console.log('Hello ' + name);
});

⬆️是函数表达式, 创建之后可以立即调用. 下面是 立即调用函数表达式的几种形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var greet = function(name) {
console.log('Hello ' + name);
}('John');

(function (name){
console.log('Hello ' + name);
})('John');

(function (name){
console.log('Hello ' + name);
}('John'));

(function greet(name){
console.log('Hello ' + name);
}('John'));

IIFE

当图片上这段代码开始执行, JS引擎看到第一行时, 将这部分代码看作函数表达式, 将其作为对象保存在内存空间, 直到执行到('John')这部分时, 匿名函数(即之前的对象)的执行环境被创建, 匿名函数内的greeting变量也被保存在这里, 而不是全局环境. 因为这个特点, IIFE在框架中有广泛的应用.

IIFE在框架中的应用:

1
2
<script src='greet.js'></script>
<script src='app.js'></script>

greet.js

1
var greeting = 'Hola';

app.js

1
2
3
4
(function(name){
var greeting = 'Hello';
console.log(greeting + ' ' + name);
}('John'));

上面部分的几段代码执行时相当于:

1
2
3
4
5
6
7
8
var greeting = 'Hola';

(function(name){
var greeting = 'Hello';
console.log(greeting + ' ' + name);
}('John'));

console.log(greeting);

IIFE2

在IIFE中定义的greeting变量在局部作用域中,不会污染全局作用域的greeting变量.

如果想在IIFE中使用全局作用域的变量,可在IIFE中声明并传入, 还可在其中改变全局作用域变量的值:

1
2
3
4
5
6
7
8
9
10
var greeting = 'Hola';

console.log(greeting); // "Hola"

(function(global, name){
global.greeting = 'Hello';
console.log(global.greeting + ' ' + name);
}(window,'John')); // "Hello John"

console.log(greeting); // "Hello"

工厂函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function makeGreeting(language) {

return function(firstname,lastname){
if (language === 'en'){
console.log('Hello ' + firstname + ' ' + lastname);
}
if (language === 'es'){
console.log('Hola ' + firstname + ' ' + lastname);
}
}

}

var greetEnglish = makeGreeting('en');
var greetSpanish = makeGreeting('es');

greetEnglish('John','Doe');
greetSpanish('Jane','Doe')

传入外层函数, 在内层函数返回, 尽管在内层函数执行时makeGreeting函数已经跳出执行栈, 但language变量依然存在于内存空间. 调用不同的language, 创造的是不同的执行环境, 因此变量存在于不同的内存空间.

closure-factory


参考