Phần này cung cấp một chút nền tảng về JavaScript để giúp bạn hiểu lý do tại sao nó như vậy.
ECMAScript là tên chính thức của JavaScript. Một cái tên mới trở nên cần thiết vì có một nhãn hiệu trên JavaScript (ban đầu được sở hữu bởi Sun, bây giờ bởi Oracle).
Người tạo ra JavaScript, Brendan Eich, không có lựa chọn nào khác ngoài việc tạo ra ngôn ngữ rất nhanh (hoặc các công nghệ tồi tệ hơn khác sẽ được Netscape áp dụng). Ông mượn từ một số ngôn ngữ lập trình: Java (cụm ngữ, giá trị nguyên thủy so với đối tượng), Scheme và AWK (các hàm hạng nhất), Self (sự thừa kế nguyên mẫu) và Perl và Python (các chuỗi, mảng và biểu thức đều đặn).
JavaScript không có xử lý ngoại lệ cho đến ECMAScript 3, điều này giải thích tại sao ngôn ngữ thường tự động chuyển đổi giá trị và thường thất bại một cách im lặng: ban đầu nó không thể ném ngoại lệ.
Một mặt, JavaScript có những nét kỳ quặc và thiếu khá nhiều chức năng (biến số phạm vi khối, mô-đun, hỗ trợ phân loại phụ, v.v.). Mặt khác, nó có một số tính năng mạnh mẽ cho phép bạn làm việc xung quanh những vấn đề này. Trong các ngôn ngữ khác, bạn học các tính năng ngôn ngữ. Trong JavaScript, bạn thường học các mẫu thay vào đó.
Với những ảnh hưởng của nó, không có gì ngạc nhiên khi JavaScript cho phép một phong cách lập trình là một sự pha trộn của lập trình chức năng (các hàm bậc cao hơn; bản đồ tích hợp, giảm, v.v.) và lập trình hướng đối tượng (các đối tượng, thừa kế).
Phần này giải thích các nguyên tắc cú pháp cơ bản của JavaScript.
Một vài ví dụ về cú pháp:
// Two slashes start single-line comments
var x; // declaring a variable
x = 3 + y; // assigning a value to the variable `x`
foo(x, y); // calling function `foo` with parameters `x` and `y`
obj.bar(3); // calling method `bar` of object `obj`
// A conditional statement
if (x === 0) { // Is `x` equal to zero?
x = 123;
}
// Defining function `baz` with parameters `a` and `b`
function baz(a, b) {
return a + b;
}
Lưu ý hai cách sử dụng khác nhau của dấu bằng:
Để hiểu cú pháp của JavaScript, bạn nên biết rằng nó có hai loại cú pháp chính: các tuyên bố và biểu thức:
var foo;
3 * 7
Sự khác biệt giữa các câu lệnh và biểu thức được minh họa tốt nhất bởi thực tế là JavaScript có hai cách khác nhau để làm if-then-else
var x;
if (y >= 0) {
x = y;
} else {
x = -y;
}
hoặc như một biểu thức:
var x = y >= 0 ? y : -y;
Bạn có thể sử dụng thứ hai như một đối số hàm (nhưng không phải là thứ nhất):
myFunction(y >= 0 ? y : -y)
Cuối cùng, bất cứ nơi nào JavaScript mong đợi một câu lệnh, bạn cũng có thể sử dụng một biểu thức; ví dụ:
foo(7, 1);
Toàn bộ đường thẳng là một câu lệnh (được gọi là câu lệnh biểu thức), nhưng cuộc gọi hàm foo ((7, 1) là một biểu thức.
Các dấu chấm là tùy chọn trong JavaScript. Tuy nhiên, tôi khuyên bạn nên luôn bao gồm chúng, bởi vì nếu không JavaScript có thể đoán sai về kết thúc của một câu lệnh. Các chi tiết được giải thích trong Automatic Semicolon Insertion.
Các dấu chấm chấm chấm kết thúc các câu lệnh, nhưng không phải là các khối. Có một trường hợp bạn sẽ thấy dấu chấm chấm sau một khối: một biểu thức hàm là một biểu thức kết thúc bằng một khối. Nếu một biểu thức như vậy xuất hiện cuối cùng trong một câu lệnh, nó được theo sau bởi dấu chấm chấm:
// Pattern: var _ = ___;
var x = 3 * 7;
var f = function () { }; // function expr. inside var decl.
JavaScript có hai loại bình luận: bình luận một dòng và bình luận nhiều dòng.
x++; // single-line comment
Các bình luận nhiều dòng được giới hạn bởi /* và */:
/* This is
a multiline
comment.
*/
Các biến trong JavaScript được tuyên bố trước khi chúng được sử dụng:
var foo; // declare variable `foo`
Bạn có thể tuyên bố một biến và gán một giá trị cùng một lúc:
var foo = 6;
Bạn cũng có thể gán giá trị cho một biến hiện có:
foo = 4; // change variable `foo`
Có các toán tử phân bổ hợp chất như +=. Hai phân bổ sau đây là tương đương:
x += 1;
x = x + 1;
Trình nhận dạng là những tên đóng các vai trò cú pháp khác nhau trong JavaScript. Ví dụ, tên của một biến là một bộ nhận dạng.
Tương tự, ký tự đầu tiên của một mã nhận dạng có thể là bất kỳ chữ cái Unicode nào, ký hiệu đô la ($), hoặc dấu gạch dưới (_). Các ký tự tiếp theo có thể là bất kỳ chữ số Unicode nào.
arg0
_tmp
$elem
π
Các nhận dạng sau đây là những từ được dành riêng, chúng là một phần của cú pháp và không thể được sử dụng như tên biến (bao gồm tên hàm và tên tham số):
Ba nhận dạng sau đây không phải là các từ được dành riêng, nhưng bạn nên đối xử với chúng như thể chúng là:
Cuối cùng, bạn cũng nên tránh xa tên của các biến toàn cầu tiêu chuẩn. Bạn có thể sử dụng chúng cho các biến địa phương mà không phá vỡ bất cứ điều gì, nhưng mã của bạn vẫn trở nên khó hiểu.
JavaScript có nhiều giá trị mà chúng ta mong đợi từ các ngôn ngữ lập trình: boolean, số, chuỗi, mảng, v.v. Tất cả các giá trị trong JavaScript đều có thuộc tính. Mỗi thuộc tính có một khóa (hoặc tên) và một giá trị. Bạn có thể nghĩ về thuộc tính như các trường của một bản ghi. Bạn sử dụng toán tử chấm (.) để đọc một thuộc tính:
value.propKey
Ví dụ, chuỗi
> var str = 'abc';
> str.length
3
Điều trước đây cũng có thể được viết như sau:
> 'abc'.length
3
The dot operator is also used to assign a value to a property:
> var obj = {}; // empty object
> obj.foo = 123; // create property `foo`, set it to 123
123
> obj.foo
123
Và bạn có thể sử dụng nó để gọi các phương thức:
> 'hello'.toUpperCase()
'HELLO'
Trong ví dụ trước đây, chúng tôi đã gọi phương thức toUpperCase() trên giá trị
JavaScript làm cho một sự phân biệt khá tùy ý giữa các giá trị:
> var obj1 = {}; // an empty object
> var obj2 = {}; // another empty object
> obj1 === obj2
false
> obj1 === obj1
true
Ngược lại, tất cả các giá trị nguyên thủy mã hóa cùng một giá trị được coi là giống nhau:
> var prim1 = 123;
> var prim2 = 123;
> prim1 === prim2
true
Hai phần tiếp theo giải thích các giá trị và đối tượng nguyên thủy chi tiết hơn.
Sau đây là tất cả các giá trị nguyên thủy (hoặc nguyên thủy ngắn gọn):
Những người nguyên thủy có những đặc điểm sau:
> 3 === 3
true
> 'abc' === 'abc'
true
### Luôn không thay đổi Các thuộc tính không thể thay đổi, thêm hoặc xóa:
> var str = 'abc';
> str.length = 1; // try to change property `length`
> str.length // ⇒ no effect
3
> str.foo = 3; // try to create property `foo`
> str.foo // ⇒ no effect, unknown property
undefined
(Đọc một thuộc tính không xác định luôn trả về không xác định.)
Tất cả các giá trị không nguyên thủy là các đối tượng.
{
firstName: 'Jane',
lastName: 'Doe'
}
Đối tượng trước có hai thuộc tính: giá trị của thuộc tính firstName là
[ 'apple', 'banana', 'cherry' ]
Mảng trước có ba phần tử có thể truy cập thông qua các chỉ số số. Ví dụ, chỉ số của
/^a+b+$/
Các đối tượng có các đặc điểm sau:
Các danh tính được so sánh; mỗi giá trị có danh tính của riêng nó:
> ({} === {}) // two different empty objects
false
> var obj1 = {};
> var obj2 = obj1;
> obj1 === obj2
true
Bạn thường có thể tự do thay đổi, thêm và loại bỏ các thuộc tính (xem các đối tượng duy nhất):
> var obj = {};
> obj.foo = 123; // add property `foo`
> obj.foo
123
Hầu hết các ngôn ngữ lập trình đều có giá trị biểu thị thông tin bị thiếu. JavaScript có hai loại
> var foo;
> foo
undefined
Các tham số bị thiếu không được xác định:
> function f(x) { return x }
> f()
undefined
Nếu bạn đọc một thuộc tính không tồn tại, bạn sẽ không xác định:
> var obj = {}; // empty object
> obj.foo
undefined
undefined và null không có thuộc tính, thậm chí cả các phương thức tiêu chuẩn như toString().
Các hàm thường cho phép bạn chỉ ra một giá trị bị thiếu thông qua undefined hoặc null. Bạn có thể làm tương tự thông qua kiểm tra rõ ràng:
if (x === undefined || x === null) {
...
}
Bạn cũng có thể khai thác thực tế rằng cả undefined và null đều được coi là sai:
if (!x) {
...
}
false, 0, NaN, và
Có hai toán tử để phân loại các giá trị: typeof chủ yếu được sử dụng cho các giá trị nguyên thủy, trong khi instanceof được sử dụng cho các đối tượng. kiểu như thế này:
typeof value
Nó trả về một chuỗi mô tả
> typeof true
'boolean'
> typeof 'abc'
'string'
> typeof {} // empty object literal
'object'
> typeof [] // empty array literal
'object'
Bảng sau liệt kê tất cả các kết quả của loại:
typeof null trả về
ví dụ của trông như thế này:
value instanceof Constr
Nó trả về true nếu value là một đối tượng đã được tạo bởi constructor Constr (xem Constructors: Factories for Objects).
> var b = new Bar(); // object created by constructor Bar
> b instanceof Bar
true
> {} instanceof Object
true
> [] instanceof Array
true
> [] instanceof Object // Array is a subconstructor of Object
true
> undefined instanceof Object
false
> null instanceof Object
false
Loại boolean nguyên thủy bao gồm các giá trị true và false. Các toán tử sau đây tạo ra boolean:
Bất cứ khi nào JavaScript mong đợi một giá trị boolean (ví dụ, cho điều kiện của câu lệnh if), bất kỳ giá trị nào cũng có thể được sử dụng. Nó sẽ được giải thích là true hoặc false. Các giá trị sau đây được giải thích là false:
Tất cả các giá trị khác (bao gồm tất cả các đối tượng!) được coi là đúng. Các giá trị được giải thích là sai được gọi là sai, và các giá trị được giải thích là đúng được gọi là đúng. Boolean ((), được gọi là một hàm, chuyển đổi tham số của nó thành boolean. Bạn có thể sử dụng nó để kiểm tra cách giải thích một giá trị:
> Boolean(undefined)
false
> Boolean(0)
false
> Boolean(3)
true
> Boolean({}) // empty object
true
> Boolean([]) // empty array
true
Các toán tử logic nhị phân trong JavaScript là mạch ngắn. nghĩa là, nếu toán tử đầu tiên đủ để xác định kết quả, thì toán tử thứ hai không được đánh giá. Ví dụ, trong các biểu thức sau, hàm foo() không bao giờ được gọi:
false && foo()
true || foo()
Hơn nữa, các toán tử logic nhị phân trả về một trong các toán tử của chúng, có thể hoặc không phải là boolean.
Nếu operand đầu tiên là false, trả lại nó. Nếu không, trả lại operand thứ hai:
> NaN && 'abc'
NaN
> 123 && 'abc'
'abc'
Nếu operand đầu tiên là true, trả về nó. Nếu không, trả về operand thứ hai:
> 'abc' || 123
'abc'
> '' || 123
123
JavaScript có hai loại bình đẳng:
Bình đẳng bình thường coi (quá) nhiều giá trị là bằng nhau (các chi tiết được giải thích trong Bình đẳng bình thường (==,!=)), có thể che giấu lỗi.
Tất cả các số trong JavaScript là dấu phẩy động:
> 1 === 1.0
true
Các số đặc biệt bao gồm:
NaN (
> Number('xyz') // 'xyz' can’t be converted to a number
NaN
Vô hạn Cũng chủ yếu là một giá trị lỗi:
> 3 / 0
Infinity
> Math.pow(2, 1024) // number too large
Infinity
Infinity lớn hơn bất kỳ số nào khác (ngoại trừ NaN). Tương tự, -Infinity nhỏ hơn bất kỳ số nào khác (ngoại trừ NaN). Điều đó làm cho các số này hữu ích như các giá trị mặc định (ví dụ, khi bạn đang tìm kiếm mức tối thiểu hoặc tối đa).
JavaScript có các toán tử số học sau đây (xem Các toán tử số học):
Đối tượng toàn cầu Math (xem Math) cung cấp nhiều hoạt động số học hơn, thông qua các hàm.
JavaScript cũng có các toán tử cho các hoạt động bitwise (ví dụ, bitwise And; xem Bitwise Operators).
Các chuỗi có thể được tạo trực tiếp thông qua các chữ cái chuỗi. Những chữ cái đó được giới hạn bằng dấu ngoặc kép đơn hoặc đôi. Backslash () thoát khỏi các ký tự và tạo ra một vài ký tự điều khiển. Dưới đây là một số ví dụ:
'abc'
"abc"
'Did she say "Hello"?'
"Did she say \"Hello\"?"
'That\'s nice!'
"That's nice!"
'Line 1\nLine 2' // newline
'Backlash: \\'
Các ký tự đơn được truy cập thông qua ngoặc kép:
> var str = 'abc';
> str[1]
'b'
Độ dài thuộc tính đếm số ký tự trong chuỗi:
> 'abc'.length
3
Giống như tất cả các nguyên thủy, các chuỗi không thể thay đổi; bạn cần tạo một chuỗi mới nếu bạn muốn thay đổi một chuỗi hiện có.
Các chuỗi được liên kết thông qua toán tử cộng (+), chuyển đổi các toán tử khác thành một chuỗi nếu một trong những toán tử là một chuỗi:
> var messageCount = 3;
> 'You have ' + messageCount + ' messages'
'You have 3 messages'
Để liên kết chuỗi trong nhiều bước, sử dụng toán tử +=:
> var str = '';
> str += 'Multiple ';
> str += 'pieces ';
> str += 'are concatenated.';
> str
'Multiple pieces are concatenated.'
Dây chuỗi có nhiều phương thức hữu ích (xem phương thức nguyên mẫu chuỗi).
> 'abc'.slice(1) // copy a substring
'bc'
> 'abc'.slice(1, 2)
'b'
> '\t xyz '.trim() // trim whitespace
'xyz'
> 'mjölnir'.toUpperCase()
'MJÖLNIR'
> 'abc'.indexOf('b') // find a string
1
> 'abc'.indexOf('x')
-1
Các điều kiện và vòng lặp trong JavaScript được giới thiệu trong các phần sau.
Tuyên bố if có một điều khoản then và một điều khoản else tùy chọn được thực hiện tùy thuộc vào một điều kiện boolean:
if (myvar === 0) {
// then
}
if (myvar === 0) {
// then
} else {
// else
}
if (myvar === 0) {
// then
} else if (myvar === 1) {
// else-if
} else if (myvar === 2) {
// else-if
} else {
// else
}
Tôi khuyên bạn nên luôn luôn sử dụng dấu ngoặc (họ biểu thị các khối không hoặc nhiều câu lệnh). Nhưng bạn không cần phải làm như vậy nếu một điều khoản chỉ là một câu lệnh duy nhất (tương tự như vậy đối với các câu lệnh dòng kiểm soát cho và trong khi):
if (x < 0) return -x;
Sau đây là lệnh chuyển đổi. Giá trị của fruit quyết định trường hợp nào được thực thi:
switch (fruit) {
case 'banana':
// ...
break;
case 'apple':
// ...
break;
default: // all other cases
// ...
}
Chuỗi for có định dạng sau:
for (⟦«init»⟧; ⟦«condition»⟧; ⟦«post_iteration»⟧)
«statement»
init được thực hiện ở đầu vòng lặp. condition được kiểm tra trước mỗi lần lặp lặp vòng lặp; nếu nó trở thành false, thì vòng lặp được kết thúc. post_iteration được thực hiện sau mỗi lần lặp vòng lặp.
Ví dụ này in tất cả các yếu tố của mảng arr trên bảng điều khiển:
for (var i=0; i < arr.length; i++) {
console.log(arr[i]);
}
Vòng lặp while tiếp tục lặp trên cơ thể của nó trong khi điều kiện của nó giữ:
// Same as for loop above:
var i = 0;
while (i < arr.length) {
console.log(arr[i]);
i++;
}
Vòng lặp do-while tiếp tục lặp trên cơ thể của nó trong khi điều kiện của nó được giữ.
do {
// ...
} while (condition);
Trong tất cả các vòng lặp:
Một cách để xác định một hàm là thông qua một tuyên bố hàm:
function add(param1, param2) {
return param1 + param2;
}
Mã trước đó định nghĩa một hàm, cộng, có hai tham số, param1 và param2, và trả về tổng của cả hai tham số.
> add(6, 1)
7
> add('a', 'b')
'ab'
Một cách khác để xác định add() là gán một biểu thức hàm cho một biến add:
var add = function (param1, param2) {
return param1 + param2;
};
Một biểu thức hàm tạo ra một giá trị và do đó có thể được sử dụng để truyền trực tiếp các hàm như các đối số cho các hàm khác:
someOtherFunction(function (p1, p2) { ... });
Tuyên bố chức năng được hoisted
function foo() {
bar(); // OK, bar is hoisted
function bar() {
...
}
}
Lưu ý rằng trong khi các tuyên bố var cũng được nâng lên (xem Các biến được nâng lên), các nhiệm vụ được thực hiện bởi chúng không phải là:
function foo() {
bar(); // Not OK, bar is still undefined
var bar = function () {
// ...
};
}
Bạn có thể gọi bất kỳ hàm nào trong JavaScript với một số lượng tùy ý của các đối số; ngôn ngữ sẽ không bao giờ phàn nàn. Tuy nhiên, nó sẽ làm cho tất cả các tham số có sẵn thông qua các đối số biến đặc biệt. các đối số trông giống như một mảng, nhưng không có bất kỳ phương thức mảng nào:
> function f() { return arguments }
> var args = f('a', 'b', 'c');
> args.length
3
> args[0] // read element at index 0
'a'
Hãy sử dụng hàm sau đây để khám phá cách xử lý quá nhiều hoặc quá ít tham số trong JavaScript (công thức toArray( được hiển thị trong Chuyển đổi các đối số thành một Array):
function f(x, y) {
console.log(x, y);
return toArray(arguments);
}
Các thông số bổ sung sẽ bị bỏ qua (ngoại trừ đối số):
> f('a', 'b', 'c')
a b
[ 'a', 'b', 'c' ]
Các tham số bị thiếu sẽ làm cho giá trị không xác định:
> f('a')
a undefined
[ 'a' ]
> f()
undefined undefined
[]
Sau đây là một mô hình phổ biến để gán các giá trị mặc định cho các tham số:
function pair(x, y) {
x = x || 0; // (1)
y = y || 0;
return [ x, y ];
}
Trong dòng (1), toán tử x trả về x nếu nó là truthy (không là null, undefined, v.v.).
> pair()
[ 0, 0 ]
> pair(3)
[ 3, 0 ]
> pair(3, 5)
[ 3, 5 ]
Nếu bạn muốn thực thi một arity (một số tham số cụ thể), bạn có thể kiểm tra arguments.length:
function pair(x, y) {
if (arguments.length !== 2) {
throw new Error('Need exactly 2 arguments');
}
...
}
arguments không phải là một mảng, nó chỉ giống như một mảng (xem Array-Like Objects and Generic Methods). Nó có chiều dài thuộc tính, và bạn có thể truy cập các phần tử của nó thông qua các chỉ mục trong ngoặc kép. Tuy nhiên, bạn không thể xóa các phần tử hoặc gọi bất kỳ phương thức mảng nào trên nó. Do đó, đôi khi bạn cần chuyển đổi các đối số thành một mảng, đó là những gì hàm sau làm (nó được giải thích trong Array-Like Objects and Generic Methods):
function toArray(arrayLikeObject) {
return Array.prototype.slice.call(arrayLikeObject);
}
Cách phổ biến nhất để xử lý ngoại lệ (xem Chương 14) là như sau:
function getPerson(id) {
if (id < 0) {
throw new Error('ID must not be negative: '+id);
}
return { id: id }; // normally: retrieved from database
}
function getPersons(ids) {
var result = [];
ids.forEach(function (id) {
try {
var person = getPerson(id);
result.push(person);
} catch (exception) {
console.log(exception);
}
});
return result;
}
Điều try bao quanh mã quan trọng, và điều catch được thực hiện nếu một ngoại lệ được ném vào bên trong điều try.
> getPersons([2, -5, 137])
[Error: ID must not be negative: -5]
[ { id: 2 }, { id: 137 } ]
Chế độ nghiêm ngặt (xem Chế độ nghiêm ngặt) cho phép nhiều cảnh báo hơn và làm cho JavaScript trở thành một ngôn ngữ sạch hơn (chế độ không nghiêm ngặt đôi khi được gọi là
'use strict';
Bạn cũng có thể bật chế độ nghiêm ngặt cho mỗi chức năng:
function functionInStrictMode() {
'use strict';
}
Trong JavaScript, bạn tuyên bố các biến thông qua var trước khi sử dụng chúng:
> var x;
> x
undefined
> y
ReferenceError: y is not defined
Bạn có thể tuyên bố và khởi tạo một số biến với một câu lệnh var duy nhất:
var x = 1, y = 2, z = 3;
Nhưng tôi khuyên bạn nên sử dụng một câu lệnh cho mỗi biến (lý do được giải thích trong Syntax).
var x = 1;
var y = 2;
var z = 3;
Bởi vì nâng (xem Các biến được nâng), thường tốt nhất là tuyên bố các biến ở đầu của một hàm.
Phạm vi của một biến luôn là hàm hoàn chỉnh (trái ngược với khối hiện tại). Ví dụ:
function foo() {
var x = -512;
if (x < 0) { // (1)
var tmp = -x;
...
}
console.log(tmp); // 512
}
Chúng ta có thể thấy rằng biến tmp không bị giới hạn trong khối bắt đầu trong dòng (1); nó tồn tại cho đến khi kết thúc hàm.
Mỗi tuyên bố biến được nâng lên: tuyên bố được di chuyển đến đầu của hàm, nhưng các bài tập mà nó thực hiện vẫn được đặt.
function foo() {
console.log(tmp); // undefined
if (false) {
var tmp = 3; // (1)
}
}
Bên trong, hàm trước được thực hiện như thế này:
function foo() {
var tmp; // hoisted declaration
console.log(tmp);
if (false) {
tmp = 3; // assignment stays put
}
}
Mỗi hàm vẫn kết nối với các biến của các hàm xung quanh nó, ngay cả sau khi nó rời khỏi phạm vi mà nó được tạo ra. Ví dụ:
function createIncrementor(start) {
return function () { // (1)
start++;
return start;
}
}
Chức năng bắt đầu ở dòng (1) rời khỏi ngữ cảnh mà trong đó nó được tạo nhưng vẫn được kết nối với phiên bản bắt đầu trực tiếp:
> var inc = createIncrementor(5);
> inc()
6
> inc()
7
> inc()
8
Một đóng là một hàm cộng với kết nối với các biến của phạm vi xung quanh.
Đôi khi bạn muốn giới thiệu một phạm vi biến mới, ví dụ, để ngăn biến trở thành toàn cầu. Trong JavaScript, bạn không thể sử dụng một khối để làm như vậy; bạn phải sử dụng một hàm. Nhưng có một mẫu để sử dụng một hàm theo cách giống như khối. Nó được gọi là IIFE (lời biểu thức hàm được gọi ngay lập tức, phát âm là
(function () { // open IIFE
var tmp = ...; // not a global variable
}()); // close IIFE
Hãy chắc chắn nhập ví dụ trước đó chính xác như được hiển thị (ngoại trừ các bình luận). Một IIFE là một biểu thức hàm được gọi ngay sau khi bạn xác định nó. Bên trong hàm, một phạm vi mới tồn tại, ngăn chặn tmp trở thành toàn cầu. Hãy tham khảo giới thiệu phạm vi mới thông qua một IIFE để biết chi tiết về IIFE.
Các kết thúc giữ kết nối của họ với các biến bên ngoài, mà đôi khi không phải là những gì bạn muốn:
var result = [];
for (var i=0; i < 5; i++) {
result.push(function () { return i }); // (1)
}
console.log(result[1]()); // 5 (not 1)
console.log(result[3]()); // 5 (not 3)
Giá trị được trả về trong dòng (1) luôn luôn là giá trị hiện tại của i, không phải giá trị mà nó có khi hàm được tạo ra. Sau khi vòng lặp được hoàn thành, i có giá trị 5, đó là lý do tại sao tất cả các hàm trong mảng trả về giá trị đó. Nếu bạn muốn hàm trong dòng (1) nhận được ảnh chụp tức thời của giá trị hiện tại của i, bạn có thể sử dụng IIFE:
for (var i=0; i < 5; i++) {
(function () {
var i2 = i; // copy current i
result.push(function () { return i2 });
}());
}
Phần này bao gồm hai cơ chế định hướng đối tượng cơ bản của JavaScript: đối tượng đơn và cấu trúc (là các nhà máy cho các đối tượng, tương tự như các lớp trong các ngôn ngữ khác).
Như tất cả các giá trị, các đối tượng có thuộc tính. Bạn có thể, trên thực tế, xem xét một đối tượng là một tập hợp các thuộc tính, trong đó mỗi thuộc tính là một cặp (khóa, giá trị).
Trong JavaScript, bạn có thể trực tiếp tạo các đối tượng đơn giản, thông qua các chữ cái đối tượng:
'use strict';
var jane = {
name: 'Jane',
describe: function () {
return 'Person named '+this.name;
}
};
Đối tượng trước có tên thuộc tính và mô tả. Bạn có thể đọc (
> jane.name // get
'Jane'
> jane.name = 'John'; // set
> jane.newProperty = 'abc'; // property created automatically
Các thuộc tính có giá trị hàm như describe được gọi là phương thức.
> jane.describe() // call method
'Person named John'
> jane.name = 'Jane';
> jane.describe()
'Person named Jane'
Các trong toán tử kiểm tra xem một thuộc tính tồn tại:
> 'newProperty' in jane
true
> 'foo' in jane
false
Nếu bạn đọc một thuộc tính không tồn tại, bạn sẽ nhận được giá trị undefined.
> jane.newProperty !== undefined
true
> jane.foo !== undefined
false
Động thái xóa loại bỏ một thuộc tính:
> delete jane.newProperty
true
> 'newProperty' in jane
false
Chìa khóa thuộc tính có thể là bất kỳ chuỗi nào. Cho đến nay, chúng ta đã thấy các khóa thuộc tính trong các chữ cái đối tượng và sau trình diễn chấm. Tuy nhiên, bạn chỉ có thể sử dụng chúng theo cách đó nếu chúng là các nhận dạng (xem nhận dạng và tên biến). Nếu bạn muốn sử dụng các chuỗi khác như các khóa, bạn phải trích dẫn chúng trong một chữ cái đối tượng và sử dụng ngoặc kép để lấy và đặt thuộc tính:
> var obj = { 'not an identifier': 123 };
> obj['not an identifier']
123
> obj['not an identifier'] = 456;
Các ngoặc kép cũng cho phép bạn tính toán khóa của một thuộc tính:
> var obj = { hello: 'world' };
> var x = 'hello';
> obj[x]
'world'
> obj['hel'+'lo']
'world'
Nếu bạn trích xuất một phương thức, nó sẽ mất kết nối với đối tượng.
Như một ví dụ, hãy quay lại đối tượng Jane trước đây:
'use strict';
var jane = {
name: 'Jane',
describe: function () {
return 'Person named '+this.name;
}
};
Chúng ta muốn trích xuất phương thức describe từ jane, đưa nó vào một biến func, và gọi nó. Tuy nhiên, điều đó không hoạt động:
> var func = jane.describe;
> func()
TypeError: Cannot read property 'name' of undefined
Giải pháp là sử dụng phương thức bind() mà tất cả các hàm đều có. Nó tạo ra một hàm mới mà nó luôn có giá trị được đưa ra:
> var func2 = jane.describe.bind(jane);
> func2()
'Person named Jane'
Mỗi hàm có biến đặc biệt của riêng nó this. Điều này là không thuận tiện nếu bạn tổ hợp một hàm bên trong một phương thức, bởi vì bạn không thể truy cập vào phương thức
var jane = {
name: 'Jane',
friends: [ 'Tarzan', 'Cheeta' ],
logHiToFriends: function () {
'use strict';
this.friends.forEach(function (friend) {
// `this` is undefined here
console.log(this.name+' says hi to '+friend);
});
}
}
Gọi logHiToFriends tạo ra lỗi:
> jane.logHiToFriends()
TypeError: Cannot read property 'name' of undefined
Chúng ta hãy xem xét hai cách để khắc phục điều này.
logHiToFriends: function () {
'use strict';
var that = this;
this.friends.forEach(function (friend) {
console.log(that.name+' says hi to '+friend);
});
}
Hoặc, forEach có một tham số thứ hai cho phép bạn cung cấp một giá trị cho điều này:
logHiToFriends: function () {
'use strict';
this.friends.forEach(function (friend) {
console.log(this.name+' says hi to '+friend);
}, this);
}
Các biểu thức hàm thường được sử dụng như là các đối số trong các cuộc gọi hàm trong JavaScript.
Cho đến nay, bạn có thể nghĩ rằng các đối tượng JavaScript chỉ là bản đồ từ chuỗi đến giá trị, một khái niệm được gợi ý bởi các chữ cái đối tượng JavaScript, trông giống như các chữ cái bản đồ / từ điển của các ngôn ngữ khác. Tuy nhiên, các đối tượng JavaScript cũng hỗ trợ một tính năng thực sự định hướng đối tượng: thừa kế. Phần này không giải thích đầy đủ cách thức thừa kế JavaScript hoạt động, nhưng nó cho bạn thấy một mô hình đơn giản để bắt đầu. Xem Chương 17 nếu bạn muốn biết thêm.
Ngoài các hàm và phương thức
// Set up instance data
function Point(x, y) {
this.x = x;
this.y = y;
}
// Methods
Point.prototype.dist = function () {
return Math.sqrt(this.x*this.x + this.y*this.y);
};
Chúng ta có thể thấy rằng một constructor có hai phần. Thứ nhất, hàm Point thiết lập dữ liệu thực tế. Thứ hai, thuộc tính Point.prototype chứa một đối tượng với các phương thức. Dữ liệu trước đó là cụ thể cho mỗi thực tế, trong khi dữ liệu sau được chia sẻ giữa tất cả các thực tế.
Để sử dụng Point, chúng ta gọi nó qua toán tử mới:
> var p = new Point(3, 5);
> p.x
3
> p.dist()
5.830951894845301
p là một trường hợp của Point:
> p instanceof Point
true
Mảng là chuỗi các phần tử có thể truy cập thông qua các chỉ số số nguyên bắt đầu từ 0.
Array literal rất hữu ích để tạo ra các mảng:
> var arr = [ 'a', 'b', 'c' ];
Mảng trước có ba yếu tố: các chuỗi
> arr[0]
'a'
> arr[0] = 'x';
> arr
[ 'x', 'b', 'c' ]
Thuộc tính length cho biết một mảng có bao nhiêu phần tử. Bạn có thể sử dụng nó để thêm các phần tử và loại bỏ các phần tử:
> var arr = ['a', 'b'];
> arr.length
2
> arr[arr.length] = 'c';
> arr
[ 'a', 'b', 'c' ]
> arr.length
3
> arr.length = 1;
> arr
[ 'a' ]
Động số in cũng hoạt động cho mảng:
> var arr = [ 'a', 'b', 'c' ];
> 1 in arr // is there an element at index 1?
true
> 5 in arr // is there an element at index 5?
false
Lưu ý rằng mảng là đối tượng và do đó có thể có thuộc tính đối tượng:
> var arr = [];
> arr.foo = 123;
> arr.foo
123
Các mảng có nhiều phương thức (xem phương thức mẫu mảng). Dưới đây là một vài ví dụ:
> var arr = [ 'a', 'b', 'c' ];
> arr.slice(1, 2) // copy elements
[ 'b' ]
> arr.slice(1)
[ 'b', 'c' ]
> arr.push('x') // append an element
4
> arr
[ 'a', 'b', 'c', 'x' ]
> arr.pop() // remove last element
'x'
> arr
[ 'a', 'b', 'c' ]
> arr.shift() // remove first element
'a'
> arr
[ 'b', 'c' ]
> arr.unshift('x') // prepend an element
3
> arr
[ 'x', 'b', 'c' ]
> arr.indexOf('b') // find the index of an element
1
> arr.indexOf('y')
-1
> arr.join('-') // all elements in a single string
'x-b-c'
> arr.join('')
'xbc'
> arr.join()
'x,b,c'
Có một số phương pháp mảng để lặp lại trên các phần tử (xem Iteration (Non-destructive)). Hai phương pháp quan trọng nhất là forEach và map.
forEach lặp lại trên một mảng và trao phần tử hiện tại và chỉ mục của nó cho một hàm:
[ 'a', 'b', 'c' ].forEach(
function (elem, index) { // (1)
console.log(index + '. ' + elem);
});
Mã trước đó tạo ra đầu ra sau:
0. a
1. b
2. c
Lưu ý rằng hàm trong dòng (1) được tự do bỏ qua các đối số.
map tạo một mảng mới bằng cách áp dụng một hàm cho mỗi phần tử của mảng hiện có:
> [1,2,3].map(function (x) { return x*x })
[ 1, 4, 9 ]
JavaScript có hỗ trợ tích hợp cho các biểu thức đều đặn.
/^abc$/
/[A-Za-z0-9]+/
> /^a+b+$/.test('aaab')
true
> /^a+b+$/.test('aaa')
false
> /a(b+)a/.exec('_abbba_aba_')
[ 'abbba', 'bbb' ]
Mảng được trả lại chứa sự khớp hoàn chỉnh ở chỉ số 0, chụp nhóm đầu tiên ở chỉ số 1, v.v. Có một cách (được thảo luận trong RegExp.prototype.exec: Capture Groups) để gọi phương thức này nhiều lần để có được tất cả các sự khớp.
> '<a> <bbb>'.replace(/<(.*?)>/g, '[$1]')
'[a] [bbb]'
Các tham số đầu tiên của thay thế phải là một biểu thức thường xuyên với một cờ /g; nếu không, chỉ có sự xuất hiện đầu tiên được thay thế.
Toán học là một đối tượng với các hàm số học.
> Math.abs(-2)
2
> Math.pow(3, 2) // 3 to the power of 2
9
> Math.max(2, -1, 5)
5
> Math.round(1.9)
2
> Math.PI // pre-defined constant for π
3.141592653589793
> Math.cos(Math.PI) // compute the cosine for 180°
-1