65.9K
CodeProject 正在变化。 阅读更多。
Home

JavaScript 术语

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (56投票s)

2011年9月1日

CPOL

8分钟阅读

viewsIcon

94438

精选 JavaScript 术语,附带解释和代码示例

引言

从 C# 转到 JavaScript,我遇到了一些新的术语,正如你所预料的那样。在这里,我将介绍其中一些新术语,并附带一些关于它们具体含义的示例。其中一些并不是真正“新”的术语,但具有不同的或特殊的含义,因此 warrants 值得在此包含。

目录

匿名

匿名函数是没有名称的函数。

正如我之前所说,ECMAScript 规范中并没有提到“匿名”这个词,所以从某种意义上说,它是开放解释的。然而,JavaScript 社区对于“无名称”的定义有明确的共识——包括CrockfordMozilla、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 中实现经典继承的方法有很多很多,但这种原型继承技术应该能说明原型的使用方式。

© . All rights reserved.