闭包
最后更新于:2022-04-01 23:10:37
闭包是作用域概念的扩展。通过闭包,函数可以访问存在函数被创建的作用域中的变量。如果这显得令人困惑,别担心:闭包一般最适合通过例子来理解。
如同[作用域](http://js101.co/javascript-101/scope.html)部分所示,函数可以访问变化的变量值。定义在循环中的函数也存在同样的行为 - 即使在函数定义之后,它依然能观察到变量的值发生了改变,导致每一个函数都引用了保存在变量中的最后值。
~~~
// Each function executed within the loop will reference
// the last value stored in i (5).
// This won't behave as we want it to - every 100 milliseconds, 5 will alert
for ( var i = 0; i < 5; i++ ) {
setTimeout(function() {
alert( i );
}, i * 100 );
}
~~~
闭包可以用来防止这种情况,通过给每一次迭代创建一个独特的作用域 - 在其作用域内保存变量的每一个独特值。
~~~
// Using a closure to create a new private scope
// fix: “close” the value of i inside createFunction, so it won't change
var createFunction = function( i ) {
return function() {
alert( i );
};
};
for ( var i = 0; i < 5; i++ ) {
setTimeout( createFunction( i ), i * 100 );
}
~~~
闭包也可以用来解决 `this` 关键字的问题,它是每个作用域的唯一值:
~~~
// Using a closure to access inner and outer object instances simultaneously.
var outerObj = {
myName: "outer",
outerFunction: function() {
// Provide a reference to outerObj through innerFunction's closure
var self = this;
var innerObj = {
myName: "inner",
innerFunction: function() {
console.log( self.myName, this.myName ); // "outer inner"
}
};
innerObj.innerFunction();
console.log( this.myName ); // "outer"
}
};
outerObj.outerFunction();
~~~
## Function.bind
当处理回调函数时,闭包也是特别有用的。但是,通常更好的做法是使用`Function.bind`,它可以避免任何作用域遍历相关的过度开销。
`Function.bind` 被用来创建一个新函数。当新函数被调用时,函数会在 `.bind()`方法中提供的 `this` 上下文中执行,并使用一系列 `.bind()` 方法中提供的参数与函数调用时提供的任何参数。
由于 `.bind()` 是在 ECMAScript 5 中添加的,它可能不会得到所有浏览器的支持,当决定是否使用它时,这是值得注意的一点。不过,我们可以使用 MDN 提供的[兼容代码](https://developer.mozilla.org/zh-CN/JavaScript/Reference/Global_Objects/Function/bind)来使 `.bind()` 正常工作。
~~~
// Shim from MDN
if (!Function.prototype.bind) {
Function.prototype.bind = function( oThis ) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5 internal
// IsCallable function
throw new TypeError( "Function.prototype.bind - what is trying to be bound is not callable" );
}
var fSlice = Array.prototype.slice,
aArgs = fSlice.call( arguments, 1 ),
fToBind = this,
fNOP = function() {},
fBound = function() {
return fToBind.apply( this instanceof fNOP
? this
: oThis || window,
aArgs.concat( fSlice.call( arguments ) ) );
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
~~~
`.bind()` 最简单的用途之一是创建一个使用特定 `this` 值的函数,而无关该函数是如何调用的。一个开发者常常出现的错误是试图从对象中提取一个方法,在随后调用该方法时期望使用原始对象作为 `this` 值。这时可以通过创建一个函数绑定原始对象来解决类似问题,如下所示:
~~~
// Let's manipulate "this" with a basic example.
var user = "johnsmith";
var module = {
getUser: function() {
return this.user;
},
user: "janedoe"
};
// module.getUser() is called where "module" is "this"
// and "module.user" is returned.
// janedoe
module.getUser();
// let's now store a reference in the global version of "this"
var getUser = module.getUser;
// getUser() called, "this" is global, "user" is returned
// johnsmith
getUser();
// store a ref with "module" bound as "this"
var boundGetUser = getUser.bind( module );
// boundGetUser() called, "module" is "this" again, "module.user" returned.
// janedoe
boundGetUser();
~~~
';
作用域
最后更新于:2022-04-01 23:10:35
作用域通常是指在指定的时间内,变量存在于一段代码中。缺乏对作用域的理解可能会导致令人沮丧的调试体验。作用域的概念是关于我们的代码中可以访问到哪些确定的函数或变量,代码的上下文和执行环境。
在 JavaScript 中,有两种类型的作用域:全局和局部作用域。
## 全局作用域
第一种作用域是全局作用域。它很容易定义。如果一个变量或函数是_全局的_,那么在程序中的任何地方都可以访问到它们。在浏览器中,全局作用域是 `window`对象。如果在函数外面声明一个变量,那么这个变量就存在全局对象中。例如:
~~~
var x = 9;
~~~
一旦该变量被定义,则可以被引用为 `window.x`,因为它存在于全局对象中,我们可以简单的引用它为 `x`。
## 局部作用域
JavaScript 也可以在每个函数体中创建局部作用域。例如:
~~~
function myFunc() {
var x = 5;
}
myFunc();
console.log( x ); // ReferenceError: x is not defined
~~~
由于 `x` 是在 `myFunc()` 中初始化,所以它只能在 `myFunc()` 中被访问,如果我们试图在 `myFunc()` 外面访问 `x`,则会得到一个引用错误。
## 注意
如果你忘记使用 `var` 关键字声明变量,那么这个变量会自动变成全局变量。所以这段代码可以运行:
~~~
function myFunc() {
x = 5;
}
myFunc();
console.log( x ); // 5
~~~
这是一个坏主意。全局变量的值可以被程序的任何部分或者其他脚本更改。这是不期望发生的,因为它会导致无法预料的副作用。
立即调用表达式(IIFE)提供了一个避免全局变量的方式。你会看到许多如 jQuery 的 JavaScript 库经常使用这种方式:
~~~
(function() {
var jQuery = { /* All my methods go here. */ };
window.jQuery = jQuery;
})();
~~~
将一切包含在一个函数中并立即调用这个函数,这意味着函数中的所有变量都被绑定在_局部作用域_中。在函数结尾部分,你可以通过将 `jQuery` 对象绑定在全局对象 `window` 上,将一些方法和属性公开出来。了解更多关于立即调用函数表达式,请查看 Ben Alman 的文章 [Immediately-Invoked Function Expression](http://benalman.com/news/2010/11/immediately-invoked-function-expression/)。
因为局部作用域通过函数而工作,任何在另一个函数中定义的函数都可以访问外部函数里的变量:
~~~
function outer() {
var x = 5;
function inner() {
console.log( x );
}
inner(); // 5
}
~~~
但是 `.outer()` 函数不能访问 `.inner()` 函数中定义的任何变量。
~~~
function outer() {
var x = 5;
function inner() {
console.log( x );
var y = 10;
}
inner(); // 5
console.log( y ); // ReferenceError: y is not defined
}
~~~
另外,在一个函数中没有使用 `var` 关键字定义的变量不是这个函数的局部变量 - JavaScript 会向上遍历作用域链(最后会到 `window` 对象)寻找之前定义的这个变量。如果这个变量没有定义,则会在全局中定义该变量,这样会导致意外的结果。
~~~
// Functions have access to variables defined in the same scope.
var foo = "hello";
var sayHello = function() {
console.log( foo );
};
sayHello(); // "hello"
console.log( foo ); // "hello"
~~~
相同名称的变量可以在不同作用域中保存不同的值:
~~~
var foo = "world";
var sayHello = function() {
var foo = "hello";
console.log( foo );
};
sayHello(); // "hello"
console.log( foo ); // "world"
~~~
当在一个函数中引用一个外部作用域定义的变量,函数可以访问在该函数定义之后发生改变的变量值。
~~~
var myFunction = function() {
var foo = "hello";
var myFn = function() {
console.log( foo );
};
foo = "world";
return myFn;
};
var f = myFunction();
f(); // "world"
~~~
这是一个更复杂的作用域例子:
~~~
(function() {
var baz = 1;
var bim = function() {
console.log( baz );
};
bar = function() {
console.log( baz );
};
})();
~~~
在这个实例中,运行:
~~~
console.log( baz ); // baz is not defined outside of the function
~~~
将会得到一个 `ReferenceError`。`baz` 仅仅是在函数中定义,并且没有暴露在全局作用域中。
~~~
bar(); // 1
~~~
`.bar()` 是在匿名函数中定义的, 但是它没有使用 `var` 关键字定义,这意味着它没有绑定到局部作用域,而是在全局作用域创建。另外,它可以访问 `baz` 变量,因为 `.bar()` 是在与 `baz` 相同的作用域定义的,所以它可以访问变量 `baz`,即使函数外部的其他代码不可以。
~~~
bim(); // ReferenceError: bim is not defined
~~~
`.bim()` 只在函数中定义的,所以它作为局部变量而不存在于全局对象中。
';
this 关键字
最后更新于:2022-04-01 23:10:32
在 JavaScript 中,如同在大部分面向对象编程语言中一样,`this` 是一个特殊的关键字,它常在被某个对象调用的方法中指向对象本身。`this` 的值可通过一系列简单的步骤来确定:
* 如果函数是通过 `Function.call()` 或者 `Function.apply()` 调用,`this` 的值将会被设置为传递给 `.call()` 或 `.apply()` 的第一个参数。如果传递给 `.call()`或 `.apply()` 的第一个参数是 `null` 或 `undefined`,`this` 会指向全局对象(在 Web 浏览器中是 `window` 对象)。
* 如果被调用的函数是由 `Function.bind()` 创建的,`this` 将会是该函数被创建时传递给 `.bind()` 的第一个参数。
* 如果函数是作为一个对象的方法被调用,`this` 将会指向那个对象。
* 否则,当函数作为一个不依附任何对象的独立函数被调用,`this` 会指向全局对象。
~~~
// A function invoked using Function.call()
var myObject = {
sayHello: function() {
console.log( "Hi! My name is " + this.myName );
},
myName: "Rebecca"
};
var secondObject = {
myName: "Colin"
};
myObject.sayHello(); // "Hi! My name is Rebecca"
myObject.sayHello.call( secondObject ); // "Hi! My name is Colin"
~~~
~~~
// A function created using Function.bind()
var myName = "the global object";
var sayHello = function() {
console.log( "Hi! My name is " + this.myName );
};
var myObject = {
myName: "Rebecca"
};
var myObjectHello = sayHello.bind( myObject );
sayHello(); // "Hi! My name is the global object"
myObjectHello(); // "Hi! My name is Rebecca"
~~~
~~~
// A function being attached to an object at runtime.
var myName = "the global object";
var sayHello = function() {
console.log( "Hi! My name is " + this.myName );
};
var myObject = {
myName: "Rebecca"
};
var secondObject = {
myName: "Colin"
};
myObject.sayHello = sayHello;
secondObject.sayHello = sayHello;
sayHello(); // "Hi! My name is the global object"
myObject.sayHello(); // "Hi! My name is Rebecca"
secondObject.sayHello(); // "Hi! My name is Colin"
~~~
当深度调用一个长命名空间的函数时,它通常会诱使你使用一个单一简短的变量来引用实际的函数,以减少你所需要键入的代码。重点是实例方法不能这么做,因为这会导致函数内的 `this` 值发生改变,从而导致不正确的结果。例如:
~~~
var myNamespace = {
myObject: {
sayHello: function() {
console.log( "Hi! My name is " + this.myName );
},
myName: "Rebecca"
}
};
var hello = myNamespace.myObject.sayHello;
hello(); // "Hi! My name is undefined"
~~~
但是,你可以安全的减少所有代码,直到方法被调用的那个对象:
~~~
var myNamespace = {
myObject: {
sayHello: function() {
console.log( "Hi! My name is " + this.myName );
},
myName: "Rebecca"
}
};
var obj = myNamespace.myObject;
obj.sayHello(); // "Hi! My name is Rebecca"
~~~
';
类型测试
最后更新于:2022-04-01 23:10:30
JavaScript 提供了一种方法来测试变量的类型。然而,其结果可能会让人迷惑 - 例如,一个数组的类型是 `object`。
当试图检测一个特定值的类型时,常见的做法是使用 `typeof` 运算符。
~~~
// Testing the type of various variables.
var myFunction = function() {
console.log( "hello" );
};
var myObject = {
foo: "bar"
};
var myArray = [ "a", "b", "c" ];
var myString = "hello";
var myNumber = 3;
var myRegExp = /(\w+)\s(\w+)/;
typeof myFunction; // "function"
typeof myObject; // "object"
typeof myArray; // "object" -- Careful!
typeof myString; // "string"
typeof myNumber; // "number"
typeof null; // "object" -- Careful!
typeof undefined; // "undefined"
typeof meh; // "undefined" -- undefined variable.
typeof myRegExp; // "function" or "object" depending on environment.
if ( myArray.push && myArray.slice && myArray.join ) {
// probably an array (this is called "duck typing")
}
if ( Object.prototype.toString.call( myArray ) === "[object Array]" ) {
// Definitely an array!
// This is widely considered as the most robust way
// to determine if a specific value is an Array.
}
~~~
';
函数
最后更新于:2022-04-01 23:10:28
函数包含需要反复执行的代码块。函数可以取零个或多个参数,并且可以可选的返回一个值。
函数可以通过各种方式创建,其中两个方式如下:
~~~
// Function declaration.
function foo() {
// Do something.
}
~~~
~~~
// Function expression.
var foo = function() {
// Do something.
};
~~~
## 使用函数
~~~
// A simple function.
var greet = function( person, greeting ) {
var text = greeting + ", " + person;
console.log( text );
};
greet( "Rebecca", "Hello" ); // "Hello, Rebecca"
~~~
~~~
// A function that returns a value.
var greet = function( person, greeting ) {
var text = greeting + ", " + person;
return text;
};
console.log( greet( "Rebecca", "Hello" ) ); // "Hello, Rebecca"
~~~
~~~
// A function that returns another function.
var greet = function( person, greeting ) {
var text = greeting + ", " + person;
return function() {
console.log( text );
};
};
var greeting = greet( "Rebecca", "Hello" );
greeting(); // "Hello, Rebecca"
~~~
## 立即调用函数表达式(IIFE)
在 JavaScript 中一个常见的模式是立即调用函数表达式。这种模式创建一个函数表达式然后立即执行。IIFE 在要避免污染全局命名空间的情况下非常有用 - 函数内声明的变量,在外部是不可见的。
~~~
// An immediately-invoked function expression.
(function() {
var foo = "Hello world";
})();
console.log( foo ); // undefined!
~~~
## 作为参数的函数
在 JavaScript 中,函数是“一等公民” - 它们可以被赋给变量或者作为参数传递给另一个函数。传递函数作为参数是 jQuery 中的惯用法。
~~~
// Passing an anonymous function as an argument.
var myFn = function( fn ) {
var result = fn();
console.log( result );
};
// Logs "hello world"
myFn( function() {
return "hello world";
});
~~~
~~~
// Passing a named function as an argument
var myFn = function( fn ) {
var result = fn();
console.log( result );
};
var myOtherFn = function() {
return "hello world";
};
myFn( myOtherFn ); // "hello world"
~~~
';
对象
最后更新于:2022-04-01 23:10:26
对象包含一个或更多键值对。键的部分可以是任何字符串。值的部分可以是任何类型的值:数字、字符串、数组、函数,甚至是另一个对象。当这些值中的一个是函数,它被称为对象的方法。否则,它们被称为属性。
事实上,JavaScript 中的所有东西几乎都是对象 - 数组,函数,数字,甚至字符串 - 它们都有属性和方法。
~~~
// Creating an object literal.
var myObject = {
sayHello: function() {
console.log( "hello" );
},
myName: "Rebecca"
};
myObject.sayHello(); // "hello"
console.log( myObject.myName ); // "Rebecca"
~~~
创建对象字面量时,注意每一个键值对的键部分可以是任何有效的 JavaScript 标识符、字符串(被引号包含)或者数字:
~~~
var myObject = {
validIdentifier: 123,
"some string": 456,
99999: 789
};
~~~
## 遍历对象的可枚举属性:
~~~
var myObject = {
validIdentifier: 123,
"some string": 456,
99999: 789
};
for ( var prop in myObject ) {
// Determine if the property is on the object itself.
// (not on the prototype)
if ( myObject.hasOwnProperty( prop ) ) {
console.log( "Property : " + prop + " ; value : " + myObject[ prop ] );
}
}
// Would log the following:
// Please note that the order is not guaranteed and may differ.
// Property : 99999 ; value : 789
// Property : validIdentifier ; value : 123
// Property : some string ; value : 456
~~~
';
数组
最后更新于:2022-04-01 23:10:23
数组是从零索引,值的有序列表。数组是一个简便的方式来存储一组相同类型的有关项(例如字符串),但实际上,一个数组可以包含多个类型的项,甚至是其他数组。
创建一个数组,可以使用数组构造函数或者字面量声明式,在声明后,可以赋给变量一系列的值。
~~~
// A simple array with constructor.
var myArray1 = new Array( "hello", "world" );
// Literal declaration, the preferred way.
var myArray2 = [ "hello", "world" ];
~~~
字面量声明式通常是更好的选择。查看[谷歌编码指南](http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml#Array_and_Object_literals)可获得更多信息。
如果值是未知的,也可以创建一个空的数组,然后通过数组方法或者访问索引来添加元素:
~~~
// Creating empty arrays and adding values
var myArray = [];
// Adds "hello" on index 0
myArray.push( "hello" );
// Adds "world" on index 1
myArray.push( "world" );
// Adds "!" on index 2
myArray[ 2 ] = "!";
~~~
`.push()` 是一个函数,它扩展数组并添加一个元素到尾端。您也可以直接通过索引添加项。缺失的指数项将会被 `undefined` 填充。
~~~
// Leaving indices
var myArray = [];
myArray[ 0 ] = "hello";
myArray[ 1 ] = "world";
myArray[ 3 ] = "!";
console.log( myArray ); // [ "hello", "world", undefined, "!" ];
~~~
如果数组的大小是未知的,`.push()` 是更安全的。您可以通过索引取值或者赋值给数组项。
~~~
// Accessing array items by index
var myArray = [ "hello", "world", "!" ];
console.log( myArray[ 2 ] ); // "!"
~~~
## 数组方法和属性
### .length
`.length` 属性用于确定数组项的数量。
~~~
// Length of an array
var myArray = [ "hello", "world", "!" ];
console.log( myArray.length ); // 3
~~~
您将需要 `.length` 属性用于遍历一个数组:
~~~
// For loops and arrays - a classic
var myArray = [ "hello", "world", "!" ];
for ( var i = 0; i < myArray.length; i = i + 1 ) {
console.log( myArray[ i ] );
}
~~~
### .concat()
通过 `.concat()` 串联两个或多个数组:
~~~
var myArray = [ 2, 3, 4 ];
var myOtherArray = [ 5, 6, 7 ];
var wholeArray = myArray.concat( myOtherArray ); // [ 2, 3, 4, 5, 6, 7 ]
~~~
### .join()
`.join()` 使用一个分隔字符拼接数组的所有元素并创建数组的字符串表示。如果没有提供分隔符(即不带参数调用 `.join()`),数组会使用逗号进行拼接。
~~~
// Joining elements
var myArray = [ "hello", "world", "!" ];
// The default separator is a comma.
console.log( myArray.join() ); // "hello,world,!"
// Any string can be used as separator...
console.log( myArray.join( " " ) ); // "hello world !";
console.log( myArray.join( "!!" ) ); // "hello!!world!!!";
// ...including an empty one.
console.log( myArray.join( "" ) ); // "helloworld!"
~~~
### .pop()
`.pop()` 移除数组的最后一个元素。它是 `.push()` 的对立方法:
~~~
// Pushing and popping
var myArray = [];
myArray.push( 0 ); // [ 0 ]
myArray.push( 2 ); // [ 0 , 2 ]
myArray.push( 7 ); // [ 0 , 2 , 7 ]
myArray.pop(); // [ 0 , 2 ]
~~~
### .reverse()
顾名思义,调用 `.reverse()` 方法后,数组中的元素按相反的顺序排列:
~~~
var myArray = [ "world" , "hello" ];
myArray.reverse(); // [ "hello", "world" ]
~~~
### .shift()
移除数组中的第一个元素。结合 `.push` 和 `.shift()`,你可以重建一个[队列](http://zh.wikipedia.org/wiki/%E9%98%9F%E5%88%97)方法:
~~~
// Queue with shift() and push()
var myArray = [];
myArray.push( 0 ); // [ 0 ]
myArray.push( 2 ); // [ 0 , 2 ]
myArray.push( 7 ); // [ 0 , 2 , 7 ]
myArray.shift(); // [ 2 , 7 ]
~~~
### .slice()
提取数组的一部分,并返回一个包含该部分的新数组。这个方法需要一个参数,起始的索引:
~~~
// Slicing
var myArray = [ 1, 2, 3, 4, 5, 6, 7, 8 ];
var newArray = myArray.slice( 3 );
console.log( myArray ); // [ 1, 2, 3, 4, 5, 6, 7, 8 ]
console.log( newArray ); // [ 4, 5, 6, 7, 8 ]
~~~
`.slice()` 方法有一个可选的第二个参数,结束的索引。
~~~
console.log( [ 1, 2, 3, 4, 5, 6, 7, 8 ].slice( 2, 5 ) ); // [ 3, 4, 5 ]
~~~
### .splice()
移除一个确定数量的元素并在给定的索引处开始添加新的元素。它至少需要三个参数:
~~~
myArray.splice( index, length, values, ... );
~~~
* _Index_ – 开始的索引。
* _Length_ – 移除的元素数量。
* _Values_ – 在索引的位置插入的值。
例如:
~~~
var myArray = [ 0, 7, 8, 5 ];
myArray.splice( 1, 2, 1, 2, 3, 4 );
console.log( myArray ); // [ 0, 1, 2, 3, 4, 5 ]
~~~
### .sort()
数组排序。它需要一个参数,一个比较函数。如果没有提供这个函数,数组默认按照升序进行排序:
~~~
// Sorting without comparing function.
var myArray = [ 3, 4, 6, 1 ];
myArray.sort(); // 1, 3, 4, 6
~~~
~~~
// Sorting with comparing function.
function descending( a, b ) {
return b - a;
}
var myArray = [ 3, 4, 6, 1 ];
myArray.sort( descending ); // [ 6, 4, 3, 1 ]
~~~
例子中的 `descending` 函数返回的值很重要。如果返回的值小于0,`a` 的位置在 `b`之前,如果值大于0则位置相反。如果值等于0,则元素的位置(与当前)相同。
### .unshift()
在数组的第一个位置插入一个元素:
~~~
var myArray = [];
myArray.unshift( 0 ); // [ 0 ]
myArray.unshift( 2 ); // [ 2 , 0 ]
myArray.unshift( 7 ); // [ 7 , 2 , 0 ]
~~~
### .forEach()
在现代浏览器中可以使用 `.forEach()` 方法遍历数组,您传递个这个方法的函数会被数组中的每个元素调用。
被传递的函数可以带三个参数:
* _Element_ – 元素本身。
* _Index_ – 元素在数组中的索引。
* _Array_ – 数组本身。
所有的参数都是可选的,但你通常至少需要一个 _Element_ 参数。
~~~
// Native .forEach()
function printElement( elem ) {
console.log( elem );
}
function printElementAndIndex( elem, index ) {
console.log( "Index " + index + ": " + elem );
}
function negateElement( elem, index, array ) {
array[ index ] = -elem;
}
myArray = [ 1, 2, 3, 4, 5 ];
// Prints all elements to the console
myArray.forEach( printElement );
// Prints "Index 0: 1", "Index 1: 2", "Index 2: 3", ...
myArray.forEach( printElementAndIndex );
// myArray is now [ -1, -2, -3, -4, -5 ]
myArray.forEach( negateElement );
~~~
';
保留字
最后更新于:2022-04-01 23:10:21
JavaScript 有一些“保留字”,或有特殊意义的单词。除非你按照它们的本意来使用,否则你应该避免在代码中使用这些字词。
* `break`
* `case`
* `catch`
* `class`
* `const`
* `continue`
* `debugger`
* `default`
* `delete`
* `do`
* `else`
* `enum`
* `export`
* `extends`
* `false`
* `finally`
* `for`
* `function`
* `if`
* `implements`
* `import`
* `in`
* `instanceof`
* `interface`
* `let`
* `new`
* `null`
* `package`
* `private`
* `protected`
* `public`
* `return`
* `static`
* `super`
* `switch`
* `this`
* `throw`
* `true`
* `try`
* `typeof`
* `var`
* `void`
* `while`
* `with`
* `yield`
';
循环
最后更新于:2022-04-01 23:10:19
循环让一块代码运行一定的次数:
~~~
for ( var i = 0; i < 5; i++ ) {
// Logs "try 0", "try 1", ..., "try 4".
console.log( "try " + i );
}
~~~
需要注意的是在循环中,变量 `i` 的不仅仅作用于循环代码块,即使在变量名前使用了关键字 `var`。在[作用域](http://js101.co/javascript-101/scope.html)部分将对作用域进行深入讨论。
## `for` 循环
一个 `for` 循环由四个语句组成,并具有以下结构:
~~~
for ( [initialization]; [conditional]; [iteration] ) {
[loopBody]
}
~~~
初始化语句(_initialization_)在循环开始前只执行一次。它是用来准备或声明任何变量的。
条件语句(_conditional_)在每次迭代之前执行,它会返回一个值用来判断循环是否继续。如果条件语句的计算结果为一个假值,则循环停止。
迭代语句(_iteration_)在每次迭代结束时执行,它给你一个机会来改变重要变量的状态。通常,这将涉及递增或递减一个计数器,从而使循环接近结束。
循环体语句(_loopBody_)是每一次循环执行的内容,它可以包含任何东西。通常,会有需要被执行的多行语句,并应包裹在一个代码块中(`{...}`)。
一个典型的 `for` 循环:
~~~
for (var i = 0, limit = 100; i < limit; i++) {
// This block will be executed 100 times.
console.log( "Currently at " + i );
// Note: The last log will be "Currently at 99".
}
~~~
## `for...in` 循环
一个 `for...in` 循环遍历一个对象的属性,针对每一个属性,循环体语句可以被执行一次。
~~~
for ( prop in obj ) {
// statements here will be executed for every key in the object
console.log( prop + ': ' + obj[ prop ] );
}
~~~
## `while` 循环
一个 `while` 循环类似于一个 `if` 语句,不同之处在于它的主体部分会继续执行,直到条件语句计算结果为一个假值。
~~~
while ( [conditional] ) {
[loopBody]
}
~~~
一个典型的 `while` 循环:
~~~
var i = 0;
while ( i < 100 ) {
// This block will be executed 100 times.
console.log( "Currently at " + i );
i++; // Increment i
}
~~~
需要注意的是计数器是在循环的主体部分递增的。将条件和增量合并也是可行的,像这样:
~~~
var i = -1;
while ( ++i < 100 ) {
// This block will be executed 100 times.
console.log( "Currently at " + i );
}
~~~
请注意计数器开始于-1,并使用前置增量符(`++i`)。
## `do-while` 循环
这几乎是与 `while` 循环完全一样的,不同的是实际上它的循环主体内容在条件测试之前至少会执行一次。
~~~
do {
[loopBody]
} while ( [conditional] )
~~~
一个 `do-while` 循环:
~~~
do {
// Even though the condition evaluates to false
// this loop's body will still execute once.
alert( "Hi there!" );
} while ( false );
~~~
这一类型的循环是少见的,因为只有极少数情况下需要盲目的执行一次循环。无论如何,意识到这一点就好。
## `break` 和 `continue`
通常的,条件语句的计算结果不是一个真值会导致循环的终止,但是也可以通过循环内部的 `break` 语句将循环在其正常运行轨道期终止:
~~~
// Stopping a loop
for ( var i = 0; i < 10; i++ ) {
if ( something ) {
break;
}
}
~~~
你可能还需要继续循环,但不执行循环主体内的部分内容。这可以通过 `continue`语句做到:
~~~
// Skipping to the next iteration of a loop
for ( var i = 0; i < 10; i++ ) {
if ( something ) {
continue;
}
// The following statement will only be executed
// if the conditional "something" has not been met
console.log( "I have been reached" );
}
~~~
';
运算符
最后更新于:2022-04-01 23:10:17
基本的运算符让你可以操作值。
~~~
// Concatenation
var foo = "hello";
var bar = "world";
console.log( foo + " " + bar ); // "hello world"
~~~
~~~
// Multiplication and division
2 * 3;
2 / 3;
~~~
~~~
// Incrementing and decrementing
// The pre-increment operator increments the operand before any further processing.
var i = 1;
console.log( ++i ); // 2 - because i was incremented before evaluation
console.log( i ); // 2
// The post-increment operator increments the operand after processing it.
var i = 1;
console.log( i++ ); // 1 - because i was evaluated to 1 and _then_ incremented
console.log( i ); // 2 - incremented after using it
~~~
## 数字和字符串操作
在 JavaScript 中,数字和字符串偶尔会表现的出人意料。
~~~
// Addition vs. Concatenation
var foo = 1;
var bar = "2";
console.log( foo + bar ); // "12"
~~~
~~~
// Coercing a string to act as a number.
var foo = 1;
var bar = "2";
console.log( foo + Number(bar) ); // 3
~~~
`Number` 构造函数被当作普通函数调用时(如上所示),会将传递给它的参数转换成数字。一元加号运算符也可以完成同样的功能:
~~~
// Forcing a string to act as a number (using the unary plus operator).
console.log( foo + +bar ); // 3
~~~
## 逻辑运算符
逻辑运算符允许通过与(`&&`)和或(`||`)运算符来对一系列的运算数进行运算。
~~~
// Logical AND and OR operators
var foo = 1;
var bar = 0;
var baz = 2;
// returns 1, which is true
foo || bar;
// returns 1, which is true
bar || foo;
// returns 0, which is false
foo && bar;
// returns 2, which is true
foo && baz;
// returns 1, which is true
baz && foo;
~~~
在上面的例子中,`||` 运算符返回第一个真值运算数的值,或者在运算数都是真值的情况下返回最后一个运算数的值。`&&` 运算符返回第一个假值运算数的值,或者当运算数都是真值的情况下返回最后一个运算数的值。
通常你会看到开发者使用逻辑运算符来代替 `if` 语句进行流程控制。例如:
~~~
// Do something with foo if foo is truthy.
foo && doSomething( foo );
// Set bar to baz if baz is truthy;
// otherwise, set it to the return value of createBar()
var bar = baz || createBar();
~~~
这种风格比较优雅和简洁,但是也可能难于阅读或使用,特别是对新手来说。在[条件代码](http://js101.co/javascript-101/conditional-code.html)部分可查看更多关于真值和假值的事情。
## 比较运算符
比较运算符允许你来测试值是否相等或者是否相同。
~~~
// Comparison operators
var foo = 1;
var bar = 0;
var baz = "1";
var bim = 2;
foo == bar; // false
foo != bar; // true
foo == baz; // true; but note that the types are different
foo === baz; // false
foo !== baz; // true
foo === parseInt( baz ); // true
foo > bim; // false
bim > baz; // true
foo <= baz; // true
~~~
有关比较运算符的更多信息,可访问[Mozilla 开发者网络](https://developer.mozilla.org/zh-CN/docs/JavaScript/Reference/Operators/Comparison_Operators "MDN - 比较运算符")。
';
条件代码
最后更新于:2022-04-01 23:10:14
有时候一个代码块应该只在一定条件下运行。流程控制 - 通过 `if` 和 `else` 代码块,让你的代码只在满足一定的条件下运行。
~~~
// Flow control
var foo = true;
var bar = false;
if ( bar ) {
// This code will never run.
console.log( "hello!" );
}
if ( bar ) {
// This code won't run.
} else {
if ( foo ) {
// This code will run.
} else {
// This code would run if foo and bar were both false.
}
}
~~~
虽然在单行 `if` 语句里,大括号不是必须的,但应该保持一致的使用它们,这样使得代码会更有可读性。
注意不要在 `if` 或 `else` 代码块中,多次定义相同名称的函数。因为这样做可能会得不到预期的结果。
## Truthy 和 Falsy
为了成功的使用流程控制,重要的一点是需要理解哪些类型的值是“truthy”,哪些是“falsy”。有时候一个值实际计算的结果和看起来应该会得到的结果不同。
~~~
// Values that evaluate to false:
false
"" // An empty string.
NaN // JavaScript's "not-a-number" variable.
null
undefined // Be careful -- undefined can be redefined!
0 // The number zero.
~~~
~~~
// Everything else evaluates to true, some examples:
"0"
"any string"
[] // An empty array.
{} // An empty object.
1 // Any non-zero number.
~~~
## 有条件的变量赋值与三元运算符
有时一个变量要根据一些条件而设定。可以使用 `if` 或 `else` 语句,但在许多情况下,三元运算符更加方便。三元运算符测试一个条件,如果条件为真,则返回一个确定的值,否则返回一个不同的值。
三元运算符:
~~~
// Set foo to 1 if bar is true; otherwise, set foo to 0:
var foo = bar ? 1 : 0;
~~~
虽然三元运算符可以在不将返回值赋值给变量的情况下使用,但这是不推荐的。
## switch 语句
比起使用一系列的 `if` 或 `else` 代码块,有时使用一个 `switch` 语句替代会更有效。`switch` 语句查看一个变量或表达式的值,并根据不同的值执行不同的代码块。
~~~
// A switch statement
switch ( foo ) {
case "bar":
alert( "the value was bar -- yay!" );
break;
case "baz":
alert( "boo baz :(" );
break;
default:
alert( "everything else is just ok" );
}
~~~
在 JavaScript 中 switch 语句有些不太流行,因为同样的行为可以通过创建一个可重用和易测试的对象来完成。
~~~
var stuffToDo = {
"bar": function() {
alert( "the value was bar -- yay!" );
},
"baz": function() {
alert( "boo baz :(" );
},
"default": function() {
alert( "everything else is just ok" );
}
};
// Check if the property exists in the object.
if ( stuffToDo[ foo ] ) {
// This code won't run.
stuffToDo[ foo ]();
} else {
// This code will run.
stuffToDo[ "default" ]();
}
~~~
对象会在 [类型](http://js101.co/javascript-101/types.html) 和 [对象](http://js101.co/javascript-101/objects.html) 部分进一步讨论。
';
类型
最后更新于:2022-04-01 23:10:12
JavaScript 中的数据类型有两类:原始类型和对象。原始类型包括:
* 字符串
* 数字
* 布尔
* null
* undefined
## 字符串
字符串是被单引号或双引号包含的文本。最佳实践是始终保持使用一种引号。有时候字符串里的引号标记会和创建字符串的引号冲突,在这种情况下,可以使用`\` 反斜线转义字符或者使用不同的引号。
~~~
// Strings can be created with double or single quotes.
var a = "I am a string";
var b = 'So am I!';
alert( a );
alert( b );
~~~
~~~
// Sometimes a string may contain quotation marks.
var statement1 = 'He said "JavaScript is awesome!"';
var statement2 = "He said \"JavaScript is awesome!\"";
~~~
## 数字
数字是任何正或负的数值。整数和浮点数之间没有区别。
~~~
// Numbers are any whole or floating point integer.
var num1 = 100;
var num2 = 100.10;
var num3 = 0.10;
~~~
## 布尔
布尔类型是 `true` 或者 `false`。
~~~
// Boolean values.
var okay = true;
var fail = false;
~~~
## null 和 undefined
`null` 和 `undefined` 是 JavaScript 中的特殊类型。 Null 类型表示一个空值,类似于许多其他语言。 Undefined 类型表示变量还没有赋值的状态,可以通过两种方式来创建: 通过使用 `undefined` 关键字或者在定义变量的时候不赋值。
~~~
// Define a null value.
var foo = null;
// Two ways to achieve an undefined value.
var bar1 = undefined;
var bar2;
~~~
## 对象
其他一切都是对象。JavaScript 有众多的 [内置对象](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects "MDN - 内置对象"),但本指南只包括:
* 对象
* 数组
* 函数
最简单的创建对象的方法是被称为对象字面量的简写语法。这些简单的对象是无序的键值对集合。对象中的键通常被称为“属性”,属性的值可以是任何有效的 JavaScript 类型,甚至可以是另一个对象。创建或访问对象的属性,我们可以使用“点号表示法”或者“括号表示法”。
~~~
// Using an empty object literal
var person1 = {};
// Assign properties using "dot notation"
person1.firstName = "John";
person1.lastName = "Doe";
// Access properties using "dot notation"
alert( person1.firstName + " " + person1.lastName );
// Creating an object with the object literal syntax:
var person2 = {
firstName: "Jane",
lastName: "Doe"
};
alert( person2.firstName + " " + person2.lastName );
var people = {};
// Assign properties using "bracket notation"
// As mentioned, objects can also have objects as a property value
people[ "person1" ] = person1;
people[ "person2" ] = person2;
// Access properties using a mix of both bracket and dot notation
alert( people[ "person1" ].firstName );
alert( people[ "person2" ].firstName );
~~~
如果被访问的属性还未定义,那么它的值将是 `undefined`。
~~~
// Properties that have not been created are undefined.
var person = { name: "John Doe" };
alert( person.email ); // undefined
~~~
在 [对象](http://js101.co/javascript-101/objects.html) 部分会进一步讨论 JavaScript 对象。
## 数组
数组是一类由它所包含的每一个项的索引排序的对象。索引开始于零,并扩展到已添加的项的数目,(项的数目)也是被称为 `.length` 的数组属性。类似一个基本对象,数组可以使用 `Array` 构造函数或者被称为数组字面量的简写语法来创建。
~~~
// Creating an array with the constructor:
var foo = new Array;
// Creating an array with the array literal syntax:
var bar = [];
~~~
在这两种语法之间有一个重要的区别。数组构造函数和数组字面量都可以在创建时加入要包含到数组的项,但是如果只是传入一个单一的数字项,数组构造函数会将该数字项当作数组的长度值。
~~~
// The array literal returns a foo.length value of 1:
var foo = [ 100 ];
alert( foo[ 0 ] ); // 100
alert( foo.length ); // 1
// The array constructor returns a bar.length value of 100:
var bar = new Array( 100 );
alert( bar[ 0 ] ); // undefined
alert( bar.length ); // 100
~~~
数组可以通过已经存在数组实例中的方法来进行相关操作。数组中的项可以通过括号和给定的索引来引用。如果索引不存在或者不包括任何值,则返回值`undefined`。
一些常见的数组方法如下所示:
~~~
// Using the push(), pop(), unshift() and shift() methods on an array.
var foo = [];
foo.push( "a" );
foo.push( "b" );
alert( foo[ 0 ] ); // a
alert( foo[ 1 ] ); // b
alert( foo.length ); // 2
foo.pop();
alert( foo[ 0 ] ); // a
alert( foo[ 1 ] ); // undefined
alert( foo.length ); // 1
foo.unshift( "z" );
alert( foo[ 0 ] ); // z
alert( foo[ 1 ] ); // a
alert( foo.length ); // 2
foo.shift();
alert( foo[ 0 ] ); // a
alert( foo[ 1 ] ); // undefined
alert( foo.length ); // 1
~~~
还有更多的方法来操作数组,一部分将在[数组](http://js101.co/javascript-101/arrays.html) 进一步讨论。更多的详细信息可在[Mozilla 开发者网络](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array "MDN - 数组参考") 找到。
## jQuery 中的类型检测
jQuery 提供了一些基本的实用方法,用于判断一个特定值的类型。类型检测会在[类型检测](http://js101.co/javascript-101/testing-type.html)部分进一步的讨论,这里有一些例子:
~~~
// Checking the type of an arbitrary value.
var myValue = [ 1, 2, 3 ];
// Using JavaScript's typeof operator to test for primitive types:
typeof myValue === "string"; // false
typeof myValue === "number"; // false
typeof myValue === "undefined"; // false
typeof myValue === "boolean"; // false
// Using strict equality operator to check for null:
myValue === null; // false
// Using jQuery's methods to check for non-primitive types:
jQuery.isFunction( myValue ); // false
jQuery.isPlainObject( myValue ); // false
jQuery.isArray( myValue ); // true
~~~
';
语法基础
最后更新于:2022-04-01 23:10:10
## 注释
JavaScript 支持单行和多行注释。注释会被 JavaScript 引擎忽略,所以它对程序的结果没有影响。使用注释为其他开发者记录代码,像 [JSDoc](http://usejsdoc.org/ "JSDoc") 这类工具库,可以帮助生成基于注释的项目文档页面。
~~~
// Single- and multi-line comments.
// This is an example of a single-line comment.
/*
* this is an example
* of a
* multi-line
* comment.
*/
~~~
## 空白
空白也被 JavaScript 引擎忽略。有许多工具可以用来去掉程序中的空白,降低了文件的整体大小和改进网络延迟。鉴于这类工具的可用性,空白应该加以利用,以使代码尽可能的易读。
~~~
// Whitespace is insignificant.
var hello = "Hello";
var world = "World!";
~~~
~~~
// Semantic whitespace promotes readability.
// Readable code is good!
var foo = function() {
for ( var i = 0; i < 10; i++ ) {
alert( i );
}
};
foo();
// This is much harder to read!
var foo=function() {for(var i=0;i<10;i++){alert(i);}};foo();
~~~
## 保留字
当声明用户定义的变量和函数时,有少量的保留字不能被使用。一些保留字已经被实现,一些被保留以供将来使用,还有一些事因为历史原因而保留。[这里](http://js101.co/javascript-101/reserved-words.html)是保留字的列表,对保留字的深入解释可以在[ MDN 的 JavaScript 参考](https://developer.mozilla.org/zh-CN/docs/JavaScript/Reference/Reserved_Words "MDN 保留字")当中找到。
## 标识符
标识符被用来给变量和函数的唯一名称,以便随后它们可以通过该名称被引用到。标识符名称必须遵循一些规则:
* 不能是保留字。
* 只能由字母,数字,美元符号和下划线组成。
* 第一个字符不能是数字。
命名标识符的最佳实践是选取一个将来也能对你或者其他开发者有意义的名称。
~~~
// Valid identifier names.
var myAwesomeVariable = "a";
var myAwesomeVariable2 = "b";
var my_awesome_variable = "c";
var $my_AwesomeVariable = "d";
var _my_awesome_variable_$ = "e";
~~~
## 变量定义
变量可以使用多个 `var` 语句来定义,或者使用单个组合的 `var` 语句。
~~~
// This works:
var test = 1;
var test2 = function() { ... };
var test3 = test2( test );
// And so does this:
var test4 = 1,
test5 = function() { ... },
test6 = test2( test );
~~~
变量可以在声明时不分配一个值,程序会给它们一个默认的值 `undefined`。
~~~
var x;
x === undefined; // true
~~~
';
运行代码
最后更新于:2022-04-01 23:10:07
## 外部
第一种也是推荐的方式是在一个外部文件(带有 `.js` 扩展名)编写代码,然后可以使用 HTML `script` 元素并通过 `src` 属性指定文件的位置来引入到网页中。当你需要将代码重复使用在其他页面时,保持 JavaScript 在一个单独的文件中可以减少代码的重复。另外它也可以让浏览器将文件缓存到客户端的计算机上,减少网页加载时间。
~~~
~~~
## 内嵌
第二种方式是直接将代码内嵌在网页中。它也是通过 HTML `script` 元素实现,但不是通过 `src` 属性指定一个文件,而是将代码放置在元素中间。虽然有些情况下可以使用这种方式,但大部分时间,最好是如上所述将我们的代码放置在外部文件中。
~~~
~~~
## 属性
最后一个选择是使用 HTML 元素的事件处理程序属性。这种方式是强烈不推荐的:
~~~
Click Me!
~~~
## 位置
在上面的前两个方式中,代码的位置是重要的,并且需要根据情况而改变。如果你添加不访问页面元素的 JavaScript,你可以放心的把脚本放在 HTML ``之前。但是,如果代码将与页面上的元素交互,就必须确保在执行代码时这些元素已经存在了。可以在下面的例子中看到这个常见的陷阱,一段查找 ID 为`hello-world` 的元素脚本将会在页面定义元素之前执行。
~~~