1. What is JavaScript ?
JavaScript,通常缩写为JS,是一种高级的,解释执行的编程语言。是一门基于原型、函数先行的语言,是一门多范式的语言,它支持面向对象编程,命令式编程,以及函数式编程。它提供语法来操控文本、数组、日期以及正则表达式等,不支持I/O,比如网络、存储和图形等,但这些都可以由它的宿主环境提供支持。它已经由ECMA(欧洲计算机制造商协会)通过ECMAScript实现语言的标准化。它被世界上的绝大多数网站所使用,也被世界主流浏览器(Chrome、IE、Firefox、Safari、Opera)支持。
- History:在上个世纪的1995年,当时的网景公司正凭借其Navigator浏览器成为Web时代开启时最著名的第一代互联网公司。由于网景公司希望能在静态HTML页面上添加一些动态效果,于是叫Brendan Eich这哥们在两周之内设计出了JavaScript语言。你没看错,这哥们只用了10天时间。
- 与Java的关系:虽然JavaScript与Java这门语言不管是在名字上,或是在语法上都有很多相似性,但这两门编程语言从设计之初就有很大的不同,JavaScript的语言设计主要受到了Self(一种基于原型的编程语言)和Scheme(一门函数式编程语言)的影响。在语法结构上它又与C语言有很多相似(例如if条件语句、while循环、switch语句、do-while循环等)
JS允许您在网页上实现复杂的事物,例如点击某处时的事件,滚动动作,动态添加元素,动画等等。您可以使用JS执行几乎所有操作。JS本身是一种质朴的,并不是那么容易实现,但是对于Web编程,JS附带了API(应用程序编程接口),它们是随时可用的代码,可以帮助您轻松实现不同的东西。
它存在两种类型的API:浏览器API和第三方API。浏览器API是与您的浏览器相关的内置API,第三方API是由外部人员(Google,Twitter,您自己的人)创建的API。
JS是一种客户端语言,意味着在用户计算机上运行的代码:当查看网页时,页面的客户端代码被下载,然后由浏览器运行和显示。在客户端,JavaScript在传统意义上被实现为一种解释语言,但在最近,它已经可以被即时编译(JIT)执行。随着最新的HTML5和CSS3语言标准的推行它还可用于游戏、桌面和移动应用程序的开发和在服务器端网络环境运行,如Node.js。
2. Starting with javascript
在编写javascript之前,你可以像在CSS中一样在index.html文件中创建链接。要实现此目的,你需要使用名为
您需要将此标记放在标记的末尾。您也可以直接在标记内的HTML文件中编写javascript。
所以,在为项目编写代码之前,让我们热身!
如果你熟悉的话,Javascript是一种像Ruby或C ++这样的对象编程语言
使用这些语言,JS对你来说不是问题。
2.1 NUMBERS(数字)
首先,在JS中,你可以使用数字进行算术运算,例如:
1 + 1 ; // = 2
0.1 + 0.2 ; // = 0.30000000000000004
8 - 1 ; // = 7
10 * 2 ; // = 20
35 / 5 ; // = 7
10 % 2 ; // = 0
你也可以执行按位操作:
1 << 2 ; // = 4
它还存在三个特殊而非数字(NaN)值的关键字:
Infinity ; // result of e.g. 1/0
- Infinity ; // result of e.g. -1/0
NaN ; // result of e.g. 0/0, stands for 'Not a Number'
最后,你可以使用布尔类型:
true;
false;
2.2 STRINGS(字符串)
当然,在JS中,你可以使用以下字符串编写和存储文本
'abc';
"Hello, world";
`Lucas is my name`;
这很容易。不需要一个字符数组或任何其他复杂的东西。
你可以轻松地进行字符串连接:
"Hello " + "world!"; // = "Hello world!"
但也做其他连接:
"1, 2, " + 3; // = "1, 2, 3"
"Hello " + ["world", "!"]; // = "Hello world,!"
您还可以使用反引号字符(`)来使用字符串模板来连接元素:
`hello ${name} !` // if variable name is Lucas, it will display : hello Lucas !
JS是一种对象编程语言,这意味着一切都是对象。如果你不熟悉对象编程的概念,你只需要记住任何JS对象都带有内置函数(也称为原型方法)和对象属性(它们是变量)。
例如,如果你编写一个字符串,则编写一个String对象,您可以访问以下方法:
// You can access characters in a string with `charAt`
"This is a string".charAt(0); // = 'T'
// ...or use `substring` to get larger pieces.
"Hello world".substring(0, 5); // = "Hello"
// `length` is a property, so don't use ().
"Hello".length; // = 5
正如你所看到的,你需要使用关键字“.”来调用方法。你不需要为每个对象学习所有方法(它会是疯狂和愚蠢的),但你始终可以使用文档访问它们。 例如,这里是字符串方法!
我们将在本课程后面详细讨论对象编程。
它还存在两个名为undefined和null的特殊关键字,它们是唯一不是对象的关键字:
null; // used to indicate a deliberate non-value
undefined; // used to indicate a value is not currently present
(although `undefined` is actually a value itself)
2.3 COMPARISONS(比较)
在JS中,相等和不等式的默认语法使用=和! 而不是 ==和!=
1 === 1; // true
2 === 1; // false
1 !== 1; // false
2 !== 1; // true
如果你使用==,你会做强制类型转换。这意味着javascript将转换一个操作数以匹配另一个操作数。
"5" == 5; // true
null == undefined; // true
"5" === 5; // false
null === undefined; // false
你还可以执行其他比较:
1 < 10; // true
1 > 10; // false
2 <= 2; // true
2 >= 2; // true
2.4 VARIABLES(变量)
与其他编程语言一样,你可以在变量中存储字符串,数字或其他元素
var someVar = 5;
正如你所看到的,你需要使用关键字var来定义新变量。在JS中,变量是动态类型的,这意味着你不需要非常明确地定义你的变量是Integer,Boolean还是String,JS会为你做。
如果省略var,则变量将在全局范围内,而不在本地范围内。
someVar = 5; // Global variable
如果你没有分配刚刚创建的变量,它们将自动设置为undefined
var someThirdVar; // = undefined
还可以使用const定义常量或使用let定义作用域变量
const name = Lucas;
let lastName;
实际上,你应该总是使用const而不是var。const和let是作用域的变量。这意味着块内的const事物与另一个块内的const事物不同。
- var定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问。
- let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。
- const用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改。
这是一个展示差异的小例子:
function greet() {
let name = "lucas";
var lastName = "Estla";
if (lastName !== "Estla") {
let name = "Caslu";
var lastName = "Nope";
}
else {
let name = "kalu";
var lastName = "Dussouchaud";
}
return [name, lastName];
}
if-else块中的变量名称与函数开头的名称不同,但每次我们再次调用var时,变量lastName都会重新定义,因此该函数将返回[“lucas”,“Dussouchaud”]
2.5 ARRAYS(数组)
如果你想在JS中操作数组,你必须像这样声明它们:
var myArray = ["Hello", 45, true];
要访问数组的元素,语法是:
myArray[1]; // = 45
在JS中,数组是可变对象,就像String对象一样,您可以访问它们的方法:
- Array可以通过索引把对应的元素修改为新的值,因此,对Array的索引进行赋值会直接修改这个Array
var arr = ['A', 'B', 'C'];
arr[1] = 99;
arr; // arr现在变为['A', 99, 'C']
- 与String类似,Array也可以通过indexOf()来搜索一个指定的元素的位置
var arr = [10, 20, '30', 'xyz'];
arr.indexOf(10); // 元素10的索引为0
arr.indexOf(20); // 元素20的索引为1
arr.indexOf(30); // 元素30没有找到,返回-1
arr.indexOf('30'); // 元素'30'的索引为2
//注意:数字 30 和字符串 '30' 是不同的元素。
- slice()就是对应String的substring()版本,它截取Array的部分元素,然后返回一个新的Array
var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
arr.slice(0, 3); // 从索引0开始,到索引3结束,但不包括索引3: ['A', 'B', 'C']
arr.slice(3); // 从索引3开始到结束: ['D', 'E', 'F', 'G']
- push()向Array的末尾添加若干元素,pop()则把Array的最后一个元素删除掉:
var arr = [1, 2];
arr.push('A', 'B'); // 返回Array新的长度: 4
arr; // [1, 2, 'A', 'B']
arr.pop(); // pop()返回'B'
arr; // [1, 2, 'A']
arr.pop(); arr.pop(); arr.pop(); // 连续pop 3次
arr; // []
arr.pop(); // 空数组继续pop不会报错,而是返回undefined
arr; // []
- 如果要往Array的头部添加若干元素,使用unshift()方法,shift()方法则把Array的第一个元素删掉:
var arr = [1, 2];
arr.unshift('A', 'B'); // 返回Array新的长度: 4
arr; // ['A', 'B', 1, 2]
arr.shift(); // 'A'
arr; // ['B', 1, 2]
arr.shift(); arr.shift(); arr.shift(); // 连续shift 3次
arr; // []
arr.shift(); // 空数组继续shift不会报错,而是返回undefined
arr; // []
- sort()可以对当前Array进行排序,它会直接修改当前Array的元素位置,直接调用时,按照默认顺序排序:
var arr = ['B', 'C', 'A'];
arr.sort();
arr; // ['A', 'B', 'C']
- reverse()把整个Array的元素给掉个个,也就是反转:
var arr = ['one', 'two', 'three'];
arr.reverse();
arr; // ['three', 'two', 'one']
- splice()方法是修改Array的“万能方法”,它可以从指定的索引开始删除若干元素,然后再从该位置添加若干元素:
var arr = ['Microsoft', 'Apple', 'Yahoo', 'AOL', 'Excite', 'Oracle'];
// 从索引2开始删除3个元素,然后再添加两个元素:
arr.splice(2, 3, 'Google', 'Facebook'); // 返回删除的元素 ['Yahoo', 'AOL', 'Excite']
arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
// 只删除,不添加:
arr.splice(2, 2); // ['Google', 'Facebook']
arr; // ['Microsoft', 'Apple', 'Oracle']
// 只添加,不删除:
arr.splice(2, 0, 'Google', 'Facebook'); // 返回[],因为没有删除任何元素
arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
- concat()方法把当前的Array和另一个Array连接起来,并返回一个新的Array
var arr = ['A', 'B', 'C'];
var added = arr.concat([1, 2, 3]);
added; // ['A', 'B', 'C', 1, 2, 3]
arr; // ['A', 'B', 'C']
- join()方法是一个非常实用的方法,它把当前Array的每个元素都用指定的字符串连接起来,然后返回连接后的字符串:
var arr = ['A', 'B', 'C', 1, 2, 3];
arr.join('-'); // 'A-B-C-1-2-3'
- 如果数组的某个元素又是一个Array,则可以形成多维数组,例如:
var arr = [[1, 2, 3], [400, 500, 600], '-'];
//上述Array包含3个元素,其中头两个元素本身也是Array。
2.6 JS OBJECTS (JS对象)
JS对象也是一种变量,它们也是可变对象,并带有不同的方法和属性。它们等同于其他语言中的映射(键对值的集合),并使用{ }定义。
var myObj = {key1: "Hello", key2: "World"};
键是字符串,但如果标识符有效则不需要引号(这意味着,如果它基本上是文本)。
与数组一样,您可以使用以下语法访问成员(称为JS对象的属性):
myObj["key1"]; // = “Hello”
myObj["key2"]; // = “World”
但您也可以使用这样的点语法(dot syntax):
myObj.key1; // = "Hello"
如果要添加/修改值,请执行以下操作:
myObj.myThirdKey = 5;
myObj.key1 = "Test";
2.7 LOGIC OPERATORS AND CONTROL STRUCTURES(逻辑运算符和控制结构)
2.7.1 条件判断
JS有著名的控制结构,如if结构:
var age = 20;
if (age >= 18) { // 如果age >= 18为true,则执行if语句块
alert('adult');
} else { // 否则执行else语句块
alert('teenager');
}
与其他语言一样,您可以通过if块执行逻辑运算:
// && is logical and, || is logical or
if (house.size == "big" && house.colour == "blue"){
house.contains = "bear";
}
if (colour == "red" || colour == "blue"){
// colour is either red or blue
}
但是你在javascript中最常做的是条件三元运算符。
三元是if条件的简写,但它会直接返回所选的值
function getFee(isMember) {
return (isMember ? "$2.00" : "$10.00");
}
console.log(getFee(true));// expected output: "$2.00"
console.log(getFee(false));// expected output: "$10.00"
console.log(getFee(1));// expected output: "$2.00"
2.7.2 循环
这是while结构:
while (true){
// An infinite loop!
}
并且do-while循环是while循环,至少运行一次:
var input;
do {
input = getInput();
} while (!isValid(input));
你也可以像C或Java中的“for”循环“”:
for (var i = 0; i < 5; i++){
// will run 5 times
}
并且还使用以下语法创建带标签的循环并Break掉:
outer:
for (var i = 0; i < 10; i++) {
for (var j = 0; j < 10; j++) {
if (i == 5 && j ==5) {
break outer;
// breaks out of outer loop instead of only the inner one
}
}
}
它还存在可以通过JS对象实现强大的循环,如for…in…:
var description = "";
var person = {fname:"Paul", lname:"Ken", age:18};
for (var x in person){
description += person[x] + " ";
} // description = 'Paul Ken 18
当然你可以实现switch选择
grade = 'B';
switch (grade) {
case 'A':
console.log("Great job");
break;
default:
console.log("Oy vey");
break;
}
2.8 Map & Set
JavaScript的默认对象表示方式{}可以视为其他语言中的Map或Dictionary的数据结构,即一组键值对。
但是JavaScript的对象有个小问题,就是键必须是字符串。但实际上Number或者其他数据类型作为键也是非常合理的。为了解决这个问题,最新的ES6规范引入了新的数据类型Map。
【注】:Map和Set是ES6标准新增的数据类型,请根据浏览器的支持情况决定是否要使用。
2.8.1 Map
Map是一组键值对的结构,具有极快的查找速度。
举个例子,假设要根据同学的名字查找对应的成绩,如果用Array实现,需要两个Array:
var names = ['Michael', 'Bob', 'Tracy'];
var scores = [95, 75, 85];
给定一个名字,要查找对应的成绩,就先要在names中找到对应的位置,再从scores取出对应的成绩,Array越长,耗时越长。
如果用Map实现,只需要一个“名字”-“成绩”的对照表,直接根据名字查找成绩,无论这个表有多大,查找速度都不会变慢。用JavaScript写一个Map如下:
var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
m.get('Michael'); // 95
初始化Map需要一个二维数组,或者直接初始化一个空Map。Map具有以下方法:
var m = new Map(); // 空Map
m.set('Adam', 67); // 添加新的key-value
m.set('Bob', 59);
m.has('Adam'); // 是否存在key 'Adam': true
m.get('Adam'); // 67
m.delete('Adam'); // 删除key 'Adam'
m.get('Adam'); // undefined
2.8.2 Set
Set和Map类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set中,没有重复的key。
要创建一个Set,需要提供一个Array作为输入,或者直接创建一个空Set:
var s1 = new Set(); // 空Set
var s2 = new Set([1, 2, 3]); // 含1, 2, 3
重复元素在Set中自动被过滤:
var s = new Set([1, 2, 3, 3, '3']);
s; // Set {1, 2, 3, "3"}
//注意数字3和字符串'3'是不同的元素。
通过add(key)方法可以添加元素到Set中,可以重复添加,但不会有效果:
s.add(4);
s; // Set {1, 2, 3, 4}
s.add(4);
s; // 仍然是 Set {1, 2, 3, 4}
通过delete(key)方法可以删除元素:
var s = new Set([1, 2, 3]);
s; // Set {1, 2, 3}
s.delete(3);
s; // Set {1, 2}
2.9 Iterable
遍历Array可以采用下标循环,遍历Map和Set就无法使用下标。为了统一集合类型,ES6标准引入了新的iterable类型,Array、Map和Set都属于iterable类型。
具有iterable类型的集合可以通过新的for … of循环来遍历:
var a = ['A', 'B', 'C'];
var s = new Set(['A', 'B', 'C']);
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
for (var x of a) { // 遍历Array
console.log(x);
}
for (var x of s) { // 遍历Set
console.log(x);
}
for (var x of m) { // 遍历Map
console.log(x[0] + '=' + x[1]);
}
你可能会有疑问,for … of循环和for … in循环有何区别?
for … in循环由于历史遗留问题,它遍历的实际上是对象的属性名称。一个Array数组实际上也是一个对象,它的每个元素的索引被视为一个属性。
当我们手动给Array对象添加了额外的属性后,for … in循环将带来意想不到的意外效果:
var a = ['A', 'B', 'C'];
a.name = 'Hello';
for (var x in a) {
console.log(x); // '0', '1', '2', 'name'
}
for … in循环将把name包括在内,但Array的length属性却不包括在内。
for … of循环则完全修复了这些问题,它只循环集合本身的元素:
var a = ['A', 'B', 'C'];
a.name = 'Hello';
for (var x of a) {
console.log(x); // 'A', 'B', 'C'
}
然而,更好的方式是直接使用iterable内置的forEach方法,它接收一个函数,每次迭代就自动回调该函数。
'use strict';
var a = ['A', 'B', 'C'];
a.forEach(function (element, index, array) {
// element: 指向当前元素的值
// index: 指向当前索引
// array: 指向Array对象本身
console.log(element + ', index = ' + index);
//A, index = 0
B, index = 1
C, index = 2
});
2.10 FUNCTIONS(函数)
JS函数使用关键字function声明。 与变量一样,类型不是必需的
function myFunction(thing){
return thing.toUpperCase();
}
myFunction("foo"); // = "FOO
JS函数是第一类对象,如字符串和数组,它们可以用作其他函数的参数。 例如
setTimeout(myFunction, 5000); //在5000ms后执行myFunction
setInterval(myFunction, 5000); //每隔5000ms执行一次myFunction
我们将myFunction作为函数setTimeout()和函数setInterval()的参数传递。 第一个功能将在5000ms后执行myFunction,第二个功能将每隔5000ms执行一次。
顺便说一句,setTimeout()和setInterval()不是JS的默认函数。 它们是由浏览器API实现的功能。当您想要使用网站页面的元素时,您将使用许多浏览器API函数。
对于将传递给其他函数的函数对象,您不必强制为它们命名,您也可以使用以下语法声明它们是匿名的:
setTimeout(function(){
// this code will be called in 5 seconds' time
}, 5000);
函数内部定义的变量作用于函数,这意味着它们不存在于逻辑结构之外(例如 for blocks, if blocks, switch blocks)!
它在JS中存在一些很酷的函数,称为自动调用匿名函数(self-invoking anonymous functions),或者也称为立即执行的匿名函数(immediately-executing anonymous functions)。它们是在声明时运行的函数,您不需要在代码中的某个位置调用它们来运行它们。
(function(){
// your code here !
})();
arguments
JavaScript还有一个免费赠送的关键字arguments,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments类似Array但它不是一个Array。利用arguments,你可以获得调用者传入的所有参数。也就是说,即使函数不定义任何参数,还是可以拿到参数的值。
实际上arguments最常用于判断传入参数的个数。你可能会看到这样的写法:
// foo(a[, b], c)
// 接收2~3个参数,b是可选参数,如果只传2个参数,b默认为null:
function foo(a, b, c) {
if (arguments.length === 2) {
// 实际拿到的参数是a和b,c为undefined
c = b; // 把b赋给c
b = null; // b变为默认值
}
// ...
}
此外,javascript还带有一些很酷的功能,如闭包(closures)。 这个词意味着在其他函数中声明的内部函数将访问外部函数的所有变量。
我们在函数A中又定义了函数B,并且,内部函数B可以引用外部函数A的参数和局部变量,当A返回函数B时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”。
返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
function sayHelloInFiveSeconds(name){
var prompt = "Hello, " + name + "!";
// 默认情况下,内部函数放在局部作用域中,就好像它们是用`var`声明的一样。
function inner(){
alert(prompt);
}
setTimeout(inner, 5000);
// setTimeout是异步的,因此sayHelloInFiveSeconds函数将立即退出,setTimeout将在之后调用inner。 但是,因为inner是“关闭”sayHelloInFiveSeconds,内部仍然可以在最终调用时访问`prompt`变量。
}
sayHelloInFiveSeconds("Adam"); // will open a popup with "Hello,Adam!" in 5s
2.11 ARROW FUNCTION(箭头函数)
为什么叫Arrow Function?因为它的定义用的就是一个箭头,使用=>符号编写函数。
param => { statements }; // anonymous function that have statements inside it
() => 'hello world'; // anonymous function without params that return 'hello world'
const myFunction = (param1, param2) => expression; // function named myFunction
正如你所看到的,你可以使用括号在函数内部执行语句,就像正常函数一样,或者只需编写return语句就可以直接调用返回值。
箭头函数听起来像是函数的简写,但它们也不像文档中所写的那样具有自己的功能,这意味着你可以轻松地引用之前的内部箭头函数并消除额外的混淆。
还结合三元运算符(ternary operator),你可以做出powerful的东西,看看这个例子:
// without arrow function and ternary
function isWordOddOrEven (word) {
if (word.length % 2 === 0) {
return 'the word is even';
} else {
return 'the word is odd';
}
}
isWordOddOrEven('bonjour'); // 'the word is odd'
// with arrow function and ternary
const isWordOddOrEven2 = word => word.length % 2 === 0
? 'the word is even'
: 'the word is odd';
isWordOddOrEven2('bonsoir'); // 'the word is odd
如果函数包含多条语句,这时候就不能省略{ ... }
和return
,并且,如果参数不是一个,就需要用括号()括起来:
(x, y, ...rest) => {
var i, sum = x + y;
for (i=0; i<rest.length; i++) {
sum += rest[i];
}
return sum;
}
2.12 OBJECTS(标准对象)
我们已经说过,对象是不同变量的集合,可以使用{}声明为key-value的键值对:
var myObj = {key1: "Hello", key2: "World"};
但是对象也可以包含函数:
var myObj = {
myFunc: function(){
return "Hello world!";
}
};
myObj.myFunc(); // = "Hello world!"
当调用附加到对象的函数时,它们可以使用this
关键字访问它们附加到的对象属性。
myObj = {
myString: "Hello world!",
myFunc: function(){
return this.myString;
}
};
myObj.myFunc(); // = "Hello world!"
对于你现有的函数,可以使用关键字“bind”将此函数添加到对象中。
var anotherFunc = function(s){
return this.myString + s;
};
myObj = anotherFunc.bind(myObj);
你还可以使用 new 关键字调用现有函数。 它将基于函数创建一个对象,该函数基本上是对象的构造函数(constructor)。
var MyConstructor = function(){
this.myNumber = 5;
};
myNewObj = new MyConstructor(); // = {myNumber: 5}
myNewObj.myNumber; // = 5