JavaScript 术语






4.94/5 (56投票s)
精选 JavaScript 术语,附带解释和代码示例
引言
从 C# 转到 JavaScript,我遇到了一些新的术语,正如你所预料的那样。在这里,我将介绍其中一些新术语,并附带一些关于它们具体含义的示例。其中一些并不是真正“新”的术语,但具有不同的或特殊的含义,因此 warrants 值得在此包含。
目录
匿名
匿名函数是没有名称的函数。
正如我之前所说,ECMAScript 规范中并没有提到“匿名”这个词,所以从某种意义上说,它是开放解释的。然而,JavaScript 社区对于“无名称”的定义有明确的共识——包括Crockford、Mozilla、Chrome 调试器和 Firebug 等来源。
所以,为避免混淆,这是一个匿名函数。它被赋值给变量 func
,因此形成了一个“函数表达式”:
var func = function(){
...
};
在接下来的情况下,该函数不是匿名的,因为它有一个名称。此外,因为它没有赋值给变量,所以它形成了一个“函数声明”。
function func(){
...
}
现在,这两种选择都会导致一个函数被定义,并通过一个名为 'func
' 的标识符来访问。所以在这两种情况下,我们都可以正常调用它。
func();
从实际角度来看,匿名函数有什么作用?答案是:简洁,仅此而已。你应该正常地为每个函数命名,以确保在调试时,你能在调用堆栈中看到它的名称。
闭包
闭包是 JavaScript 的核心概念,应该是一个 JavaScript 程序员首先要学习的东西。它们可以非常强大,并允许我们模仿其他编程语言的许多模式。
闭包是一个函数作用域(一组变量),在函数返回后仍然保持活性。
换句话说,通过巧妙地使用内部函数,我们可以在外部函数返回后仍然保留对其变量的引用。所以,一个快速示例。
function outerFunc(){
var name = "bob";
return function innerFunc(){
alert("hello " + name);
};
}
// Execute outerFunc which returns innerFunc
var test = outerFunc();
// Execute innerFunc, and even though outerFunc has already exited,
// its local vars are still in memory (name = 'bob')
test(); // 'hello bob'
现在,这里有一个更实际的例子。JavaScript 没有 private
变量。然而,我们可以通过将 private
变量嵌入闭包中来创建私有变量的概念——只有闭包内部的函数才能访问它们。工作原理如下:
function carFactory(make, model) {
// private vars
var count = 0;
// private functions
function isBatteryDead() {
return true;
}
// public vars and functions - wrapped up in an object and returned
return {
make: make,
model: model,
start: function start(){
if(!isBatteryDead()) {
count++; //Increment this private variable
}
}
};
}
// Create a car
var fordFiesta = carFactory('ford', 'fiesta');
fordFiesta.start();
这是创建 private
成员的强大方法,但不幸的是,缺点是每次使用 carFactory
创建汽车时,都会创建一个全新的成员函数副本——而不是像我们在 C# 中习惯的那样重用函数。不过,我们可以通过使用原型来实现这一点,稍后会解释。
请注意,返回内部函数并不是创建闭包的唯一方法;我们所需要做的就是让作用域在外部函数返回后仍然保持活性。以下是其他一些可能发生的情况。
示例 1 - 使用 setTimeout
function outerFunc(){
var name = "bob";
setTimeout(function(){
alert(name); //Happens second
}, 0);
}
outerFunc();
alert("hello"); //Happens first
上述代码的输出是“hello
”的警报,然后是“bob
”的警报。所以即使 outerFunc
退出后,我们仍然可以看到它内部一个变量的警报。
示例 2 - 赋值给外部作用域
var ref;
function outerFunc(){
var name = "bob";
ref = function innerFunc(){
alert(name);
};
}
outerFunc();
ref();
这个可能看起来有点复杂,但其实并不。我们只是创建了一个名为 'ref
' 的变量,它将引用内部函数。当外部函数执行时,它将内部函数赋值给 'ref
' 变量,然后返回。然后我们可以查看 'ref
' 变量来访问内部函数,以及它作用域中的变量。
柯里化
柯里化 (Currying) 是一种将具有多个参数的函数转换为具有较少参数的函数的技术。当你已经知道一个或多个参数的值,并且希望得到一个只接受你不知道的参数的函数时,就可以使用这种技术——这样你就无需在调用时不断地传入相同的参数。在 JavaScript 中,由于函数是第一等公民,可以随意传递,因此这很容易实现。
假设我们有一个接受多个参数的普通函数,比如这个设置属性的 jQuery 函数。
function attr(key, value) {
// Sets an attribute on some element
}
jQuery("#bob").attr("title", "this is the title");
柯里化将涉及以下操作,即用一个参数更少的函数包装 attr
函数,并允许我们固定两个参数的值(jQuery 对象和属性名称)。
function getAttrFn(elem, key) {
return function(value){
elem.attr(key, value);
};
}
上面的 getAttrFn
之所以有效,是因为我们创建了一个闭包,其中元素和键被存储起来,直到返回的函数被执行。它的用法如下:
var setTitle = getAttrFn(jQuery("#bob"), "title");
setTitle("this is the title"); // Sets the title of element #bob
这种技术的一种用途是创建符合所需签名的事件处理函数,同时将其他有用的参数传递给现有函数。
然而,一个缺点是,如果使用非常频繁,可能会注意到性能下降——毕竟,每次柯里化我们都会在调用堆栈中添加一个额外的函数调用。
提升 (Hoisting)
变量提升 (Variable Hoisting)
提升是指 JavaScript 运行时通过有效地移动声明它们的行来对变量执行的奇怪操作。这有助于解释为什么(以及如何)没有块级作用域,只有函数作用域——因为变量声明被“提升”到了函数的开头。
因此,例如,我们可以观察到以下现象:
1. var a = 1;
2. function hello() {
3. alert(a); // undefined (not 1!)
4. var a = 2;
5. alert(a); // 2
6. }
在此示例中,在函数 "hello
" 中定义的变量在函数顶部(即第 3 行)被定义。解释器实质上执行了以下代码:
1. var a = 1;
2. function hello() {
3. var a = undefined;
3. alert(a); // undefined (not 1!)
4. a = 2;
5. alert(a); // 2
6. }
变量 a 被提升到函数顶部。这就是为什么在 JavaScript 中,我们应该始终尝试在函数开头声明变量,以便这种行为变得显而易见。JSLint 强制执行这一点。
这是另一个有点令人困惑的例子,一个 for
循环:
function hello() {
for (var i=0; i<10; i++) {
// i = 0 to 9
}
alert(i); //10
}
这发生的原因相同,即变量 i
被提升到函数顶部,而不是仅限于 for
循环内部的作用域。
函数提升 (Functional Hoisting)
函数的提升是相似的,但不完全相同,因为函数表达式和函数声明之间有一个细微的区别。函数表达式遵循与变量相同的提升规则:
expression(); // undefined
var expression = function() {
console.log("hello");
};
……而函数声明则被完全提升到顶部——换句话说,它们从其外部函数的开始就可以使用了。
declaration(); // hello
function declaration() {
console.log("hello");
}
类型转换
类型转换 (Type coercion) 是将一种数据类型转换为另一种数据类型。在 JavaScript 中,这通常应该避免(双等号强制进行类型转换,导致奇怪的效果;三等号不执行任何转换,因此是类型安全的——这更简单易懂)。
这种概括的例外是转换为 Boolean
时。这是因为 JavaScript 中的任何值都是“假值”(falsy) 或“真值”(truthy)——也就是说,它可以被转换为布尔值,以便在 if
语句等中使用。以下是真值值的列表——其他所有值都是假值:
- True
- 不等于 0 的数字
- 非空字符串
- 对象(即使没有属性)
- 函数
您可以使用 !!
在调试器中测试一个变量是真值还是假值,例如:
var a = 0;
console.log(!!a); // false
最里面的感叹号将值转换为布尔值并获取其相反值。最外面的感叹号然后反转该结果。
或者,您可以更明确地转换为布尔值:
var a = 0;
console.log(Boolean(a)); //false
范围
作用域 (Scope) 在本文中被提及,因为作用域始终是函数的。对于任何类型的循环、if
语句等,都没有单独的块级作用域。无论变量在哪里声明,它的声明(但不是赋值)都会被提升到其函数顶部。但是,由于闭包的存在,作用域不一定是最近的函数。有关更多解释,请返回闭包和提升!
如果您想创建单独的作用域,可以创建一个自执行函数。
自执行函数 (Self-executing Function)
自执行函数是同时定义并立即调用的函数。当您想创建“块级作用域”时,这很有用——在其中定义的变量在外部不可用。此外,由于闭包,它可以看到外部定义的变量——这与其它语言中的块级作用域相同。
(function(){
var x = "hello";
}());
console.log(x); // undefined
这
在 C#(以及大多数面向对象的语言)中,this
指的是当前对象。但在 JavaScript 中,this
仅仅指代调用它的函数所属的对象。如果函数没有在任何对象上调用,那么 this
就指向 window 对象(这是完全自然的——毕竟,函数本身就是第一等对象,不需要“所有者”对象)。
示例 1 - 全局作用域
function whatIsThis(){
console.log(this);
}
whatIsThis(); // Window (global scope)
示例 2 - 声明为对象属性的函数
var obj = {
whatIsThis: function(){
console.log(this);
}
};
obj.whatIsThis(); // obj
示例 3 - 在任意对象上调用函数
var obj = { };
function whatIsThis(){
console.log(this);
}
whatIsThis.call(obj); // obj
原型
JavaScript 是一种无类别的面向对象语言。但如果它是无类别的,我们如何定义对象应该是什么样的呢?答案是原型。原型是分配给函数 'prototype
' 属性的对象。然后该函数可以用作构造函数,原型就成为结果对象行为的模型。
var Vehicle = function () {
...
};
Vehicle.prototype = {
started: false,
start: function(){
//do stuff
}
};
var c = new Vehicle();
在函数调用前使用 new
运算符会执行一系列操作:
- 创建一个新的空对象
- 新对象指向原型
- 函数以对象作为其上下文执行(‘
this
’指向它) - 返回该对象
此外,可以通过将“base
”原型复制到“super
”对象来创建继承模式。
例如,假设 Vehicle
是基类(我正在努力避免使用“类”这个词!),而 Car
应该继承它,我们将 Vehicle
的一个实例分配给 Car
的原型:
var Car = function(){
...
};
Car.prototype = new Vehicle();
现在我们已经设置好了 Car
,我们可以通过向其原型添加一些额外的函数来扩展它:
Car.prototype.OpenSunroof = function() { ... };
在 JavaScript 中实现经典继承的方法有很多很多,但这种原型继承技术应该能说明原型的使用方式。