新人培训课程·重点知识

Javascript 基础

2016-04-04  本文已影响73人  wanqijian

Javascript 基础

语法

语法概述

一些代码的示例:

// javascript 代码示例

var x; // 声明一个变量

x = 3; // 给一个变量赋值

foo(x, y); // 调用方法,两个参数 x、y

obj.bar(3); // 调用对象obj的方法,传参数3

if ( x === 0) { // 判断 x 的值是否为0
    x = 123;
}

// 定义名称为 baz 的方法,有两个参数,分别为 a 与 b
function baz(a, b) {
    return a + b;
}

注意等于号的两种不同的用户。

语句和表达式

要理解 JavaScript 的语法,需要认识他的两大语法类别:语句和表达式。

语句的方式:

// 语句的方式
var x;
if (y >= 0) {
    x = y;
} else {
    x = -y;
}

表达式的方式:

// 表达式的方式
var x = y >= 0 ? y : -y;

表达式的方式可以用户函数的参数,而语句的方式不可以作为参数

// 表达式作为参数
myFunction(y >= 0 ? y : -y);

总之,在 JavaScript 中,表达式可以用在所有需要语句的地方。

分号

在 JavaScript 中,分号是可选的。然而,我推荐一直带上她,要不 JavaScript 会猜错语句的结束位置。
分号用户结束语句,而不是结束块。有一种情况你会看到分号出现在块之后:函数表达式作为一个表达式时。如果这样的表达式出现在语句的最后,它后面就会跟上一个分号:

// Pattern: var _ = ___;
var x = 3 + x;
var f = function () { };

注释

JavaScript 有两种类型的注释,单行注释和多行注释,单行注释由两个斜杠 // 开始;多行注释 /* 开始, */ 结束。

变量和赋值

JavaScript 里的变量在声明后使用:

var foo; // 生命变量

赋值

变量声明和赋值可以同时进行:

var foo = 0;

也可以为一个已有变量进行赋值:

foo = 1;

复合赋值运行符

在 JavaScript 中还会有符合赋值预算符,如 +=、-=...

x += 1;
x = x + 1;

标识符与变量名

标识符是 JavaScript 中各种语法的名称。例如,变量名称就是一个标识符。标识符是区分大小写。

大致来讲,标识符的第一个字符可以是任意的 Unicode 字符、美元符($),或者下划线(_)。后面的字符除此之外还可以为任意 Unicode 数字。因此,以下这些都是合法的标识符:

arg0
_temp
&element
n

以下是标识符为保留字————它们是语法的一部分,不能作为变量名称使用:

abstract    arguments   boolean break   byte
case    catch   char    class*  const
continue    debugger    default delete  do
double  else    enum*   eval    export*
extends*    false   final   finally float
for function    goto    if  implements
import* in  instanceof  int interface
let long    native  new null
package private protected   public  return
short   static  super*  switch  synchronized
this    throw   throws  transient   true
try typeof  var void    volatile
while   with    yield       

以下三个标识符不是保留字,但同样需要视为保留字:

Infinity
NaN
undefined

最后,对一些标准的全局变量名,同样需要避开。虽然它们用作局部变量时不会音像什么,但是还是会使你的代码变得让人困惑。

JavaScript 有很多值,都是我们预期的编程语言的值:布尔值、数字、字符串、数组等。在 JavaScript 中所有的值都有属性。每一个属性都有一个 key 和一个 value。
可以认为,属性就像是一条记录的字段。通过(.)操作符可以读取属性:

value.propKey

例如:字符串‘abc’有length这个属性:

> 'abc'.length
3

点操作符同样可以用于给属性赋值:

> var obj = {};
> obj.foo = 123;
123
> obj.foo
123

我们也可以通过点操作符来调用方法:

> 'hello'.toUpperCase();
'HELLO'

原始值和对象

JavaScript 中,对值得区分有点自由。

> var obj1 = {};
> var obj2 = {};
> obj1 === obj2;
false
> obj1 === obj1;
true

然而所有的原始值,只要实际值相同,则他们就相等:

> var value1 = 123;
> var value2 = 123;
> value1 === value2
true

原始值

以下即为所有的原始值

原始值具有以下特点:

  1. 按值进行比较:比较实际的值
  2. 不可改变:原始值的属性不可以改变

对象

所有非原始值都是对象。最常见的对象如下:

{
    firstName: ‘Jane’,
    lastName: 'Doe'
}

上述对象有两个属性,属性 firstName 的值是 Jane,属性 lastName 的值是 Doe。

['apple', 'banana', 'cherry']

上面的数组有3个元素,可以通过数字索引来访问它们。

/^a+b+$/

对象具有以下特点

  1. 按引用进行比较
    比较身份标识:每个值都有不同的身份标识:
> {} === {}
false
> var obj1 = {};
> var obj2 = obj1;
> obj1 === obj2
true
  1. 默认可变
    对象属性可以很自由地被改变、添加和移除
> var obj = {};
> obj.foo = 123;
> obj.foo
123

undefined 和 null

大多数编程语言都会有一些值表示丢失的信息。 JavaScript 有两个类似的空值,undefined 和 null

> var foo;
> foo
undefined

丢失的参数也会是 undefined:

> function f(x) { return x}
> f()
undefined

访问不存在的属性,也会得到 undefined:

> var obj = {};
> obj.foo
undefined

undefined 和 null 没有属性,甚至连 toString() 这种标准方法都没有

检查 undefined 或 null

通常,函数允许通过 undefined 或 null 来表示缺失的值。可以通过显式的检查来做到同样的事情:

if (x === undefined || x === null) {
    ...   
}

也可以利用 undefined 和 null 都可被视为 false 这一事实来处理:

if (!x) {
    ../   
}

false,0, NaN, 和 '' 都可被视为 false。

使用 typeof 和 instanceof 对值分类

有两种对值进行分类的操作符:typeof 主要用于原始值,instanceof 用于对象。

typeof 用法如下:

typeof value

它的返回值会是一个表示这个值类型的字符串。如:

> typeof true
'boolean'
> typeof 'abc'
'string'
> typeof {}
'object'
> typeof []
'object'

typeof 会得到的所有结果:

操作数 结果
undefined 'undefined'
null object
布尔值 boolean
数字 number
字符串 string
函数 function
所有其他的常规值 object
引擎创建的值 ....

instanceof 用法形如:

value instanceof Constr

如果 value 是一个通过 Constr 构造器创建的对象,则返回 true。

> var b = new Bar();
> b instanceof Bar
true
> {} instanceof Object
true
> [] instanceof Object
true
> undefined instanceof Object
false
> null instanceof Object
false

布尔值

原始布尔值类型包含 true 和 false 两个值。以下运算符会产生布尔值。

真值与假值

在 JavaScript 中,可以使用任意值来表示布尔值。它们都会被解释成 true 或 false。

以下值会会解释成 false

其他所有的值都会被当做 true。被解释为 false 的值可被称为假值,被解释为 true的值可被称为真值。 Boolean() 作为函数调用时,会将被传入的参数转换为一个布尔值。

> Boolean(undefined)
false
> Boolean(0)
false
> Boolean(3)
true
> Boolean({})
true
> Boolean([])
true

二元逻辑运算符

JavaScript 中的二元逻辑运算符是短路的。因为如果第一个运算符就足以确定结果的话,则不会对第二个运算符做运算。

false && foo()
true || foo()

此外,二元逻辑运算符会返回运算数中的一个————可能是一个布尔值,也可能不是。对真假的检查将会用于确定返回哪一个。

  1. 与(&&)。如果第一个运算数是假值,返回它。否则,返回第二个运算数。
  2. 或(||)。如果第一个运算数是真值,返回它。否则,返回第二个运算数。

数字

JavaScript 中所有的数字都是浮点数:

> 1 === 1.0
true

也包含一些特殊的数字

NaN ("not a number")

一个错误的数字

> Number('xyz')
NaN

infinity

多数情况下也是第一个错误的值:

> 3/0
Infinity
> Math.pow(2, 1024) // number too large
Infinity

Infinity 比任何一个数都要大。同样的,-Infinity 比任何一个数都要小。这使得这两个数字常用来作为默认值,最大值,最小值。

运算符

JavaScript 具有如下算术运算符。

字符串

字符串可以直接通过字符串字面量来创建。这些字面量限定在单引号或双引号之内。反斜杠(\)用于转义字符及产生一些控制字符。

'abc'
"abc"
‘Did she say "Hello"?’
"Did she sya \"Hello\"?"
'That\'s nice!'
"That's nice!"
'Line 1\nLiune 2'
'Backlash: \\'

可以通过方括号来访问字符串中的单个字符:

> var str = 'atr'
> str[1]
'b'

字符串的 length 属性可以对字符的个数进行计数:

> 'abc'.length
3

像所有的原始值一样,字符串是不可变得;如果要改变一个已有的字符串,必须创建一个新的才行。

字符串运算符

字符串可以通过加号(+)进行连接,如果其中一个运算数是字符串的话,另一个运算数将转换为字符串:

> var messageCount = 3;
> 'You have ' + messageCount + ' messages'
'You have 3 messages'

要在多个步骤中连接字符串,可以使用 += 运算符

> var str = '';
> str += 'Mutiple ';
> str += 'pieces ';
> str += 'are concatenated.';
> str
'Mutiple pieces are concatenated.'

字符串的方法

字符串有一些常用的方法。

> 'abc'.slice(1)
'bc'
> 'abc'.slice(1,2)
'b'
> '\t xyz  '.trim()
'xyz'
> 'mjolnir'.toUpperCase()
'MJOLNIR'
> 'abc'.indexOf('b')
1
> 'abc'.indexOf('d')
-1

语句

JavaScript 中的条件和循环语句将在出场。

条件语句

if 语句有一个 then 从句以及一个可选的 else 从句,具体的执行取决于布尔条件:

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   
}

推荐始终使用大括号。不过如果从句仅有一个单独的语句时可以不必这么做:

if (x < 0) reutrn -x; 

以下是 switch 语句, fruit 的值会决定要执行哪个 case:

switch (fruit) {
    case 'banana':
        // ...
        break;
    case 'apple':
        // ...
        break;
    default:
        // ...   
}

case 之后跟的运算数可以是任意表达式:在 switch 里的参数会通过 === 来进行比较。

循环语句

for 循环有如下格式:

for ([init]; [codition]; [post_iteration])
    <statement>

初始化会在循环开始前执行。条件会在每次循环迭代之前做检查,如果是 false 则终止循环。后迭代会在每次循环迭代后执行。

for (var i=0; i<arr.lenght; i++) {
    console.log(arr[i]);   
}

while 循环语句在条件成立的时候回持续循环

var i=0;
while (i<arr.length) {
    console.log(arr[i]);
    i++;
}

do-while 循环语句在条件成立时会持续循环。由于条件跟在代码体之后,所以这些代码体至少会执行一次:

do {
    // ...   
} while (conditrion);

有两条语句适合于所有的循环方式:

函数

可以通过函数声明的方式来定义哈数:

function add(param1, param2) {
    return param1 + param2;   
}

上面的代码定义了函数 add,它有两个参数:param1 和 param2,返回值是这两个参数的和。可以这样调用这个函数:

> add(6, 1)
7
> add('a', 'b')
'ab'

除此之外,我们还可以通过给变量 add 赋值为函数表达式的方式来定义 add 函数:

var add = function (param1, param2) {
    return param1 + param2;  
};

函数表达式会产生一个值,因此可以将函数作为参数直接传递给另外的函数:

someOtherFunction(function (p1, p2) {...});

函数声明的提升特性

函数声明基友提升特性————它们的实体会被移动到所在作用于的开始处。这样我们就可以引用后面声明的函数。

function foo() {
    bar();
    function bar (){
        ...
    }   
}

注意, var 声明也具有提升的特性,但通过它们执行的赋值却不具备该特性:

function foo() {
    bar();
    var bar = function() {
        // ...  
    };
}

特殊的变量 arguments

在 JavaScript 中,函数的所有参数都可以被自由调用,它会通过 arguments 变量来是所有参数可用。 arguments 看起来像个数组,但不具备数组的方法:

> function f() { return arguments}
> var args = f('a', 'b', 'c');
> args.length
3
> args[0]
'a'

参数太多或太少

我们可以通过以下函数来探知,在 JavaScript 中,函数参数太多或太少是如何处理:

function f(x, y) {
    console.log(x, y);
    return toArray(arguments);   
}

额外的参数会被忽略(arguments 除外):

> f('a', 'b', 'c')
a b
['a', 'b', 'c']

丢失的参数会得到 undefined 这个值:

> f('a')
a undefined
['a']
> f()
undefined undefined
[]

可选参数

以下是一个给参数附上默认值的通用模式:

function pair(x, y) {
    x = x || 0;
    y = y || 0;
    return [x, y];   
}

|| 运算符会在 x 为真值的时候返回 x,否则,会返回第二个运算数:

> pair()
[0, 0]
> pair(3)
[3, 0]
> pair(3, 5)
[3, 5]

强制参数长度

如果想强制一个参数长度,可以通过 arguments.length 来检查:

function pair(x, y) {
    if (arguments.lenght !== 2) {
        throw new Error('Need exactly 2 arguments');   
    }   
}

将 arguments 转换为数组

arguments 不是数组,它只是类似数组。它有 length 属性,可以通过方括号去访问它的元素。不能移除它的元素,也不能对它调用数组的方法。因此,有时候会需要将它转换成数组:

function toArray(arrayLikeObjecy) {
    return Array.prototype.slice.call(arrayLikeObject);
}

异常捕获

最常见的捕获异常的方法如下:

function getPerson(id) {
    if (id<0) {
        throw new Error('ID must not be negative: ' + id);   
    }   
    return { id: id};
}
function getPersons(ids) {
    var result = [];
    ids.forEach(function (id) {
        try {
            var person = getPerson(id);
            result.push(person);   
        } catch(exception) {
            console.log(exception);   
        }
    });
    return result;   
}

使用 try 语句包裹关键代码,如果 try 语句有异常会被抛出那么 catch 语句就会执行。

严格模式

严格模式激活更多的警告以及使 JavaScript 变得更干净。要切换到严格模式,在 JavaScript 文件或者 script 标签第一行输入:

‘use strict’;

你也可以在每一个函数中激活严格模式:

function functionInStrictMode() {
    'use strict';        
}

变量作用域和闭包

在 JavaScript 中,通过在变量前使用 var 语句声明变量:

> var x;
> var x=3;
> var y=4;
ReferenceError: y is not defined

你可以使用单个 var 语句声明和初始化多个变量:

var x=1, y=2, z=3;

但是我推荐使用单独声明每一个变量。因此,之前的语句重写为:

var x=1;
var y=2;
var z=3;

变量在函数的作用域

一个变量的作用域总是完整的函数(函数代码块)

function foo () {
    var x = -123
    if (x<0){
        var tmp = -x;
        ...
    }
    console.log(tmp); // 123
}

变量的提升特性

所有变量声明都会被提升:声明会被移动到函数的开始处,而赋值则仍然会在原来的位置进行。

function foo() {
    console.log(tmp); // undefined 
    if (false) {
        var tmp=3;    
    }   
}

然而在程序内部,上述函数的执行过程其实是这样的:

function foo(){
    var tmp;
    console.log(tmp);
    if (false) {
        tmp = 3;    
    }     
}

闭包

每一个函数都和它周围的变量保持着连接,哪怕它离开被创建时的作用域也是如此:

function createIncrementor(start) {
    return function(){
        start++;
        return start;   
    }   
}

函数开始被创建,在创建结束后即离开它的上下文环境,但它仍然保持着和 start 的连接:

> var inc = createIncrementor(5);
> inc()
6
> inc()
7
> inc()
8

函数以及它所连接的周围作用域中的变量即为闭包。所以,createIncrementor()的返回值其实就是一个闭包。

IIFE 模式:引入一个新的作用域

有时你会想要引入一个新的作用域,例如,防止一个变量编程全局变量。在 JavaScript 中,不能通过块来做,必须使用函数。不过有一种模式可以将函数当做类似块的方式来使用。这种模式被称作为 IIFE(立即调用函数表达式):

(function () { // open IIFE
   var tmp = ...; // not a global variable
}()); // close IIFE

IIFE 是一个定义之后就被立即调用的函数表达式。在函数内部,会有一个新的作用域,以防止 tmp 变成全局变量。

IIFE 用例:闭包造成的无意共享

闭包会持续地保持与外部变量的连接,而这有时候并不是你想要的:

var result = [];
for (var i=0; i<5; i++) {
    result.push(function(){ return i});   
}
console.log(result[1]()); // 5 not 1
console.log(result[3]()); // 5 not 3

返回值总是 i 的当前值,而并非函数被创建时的值。在循环结束之后,i 的值为5,所以数组中所有的函数都返回这个数值。如果你想要这行函数获得当前 i 值得一个快照,那么你可以使用 IIFE:

for (var i=0; i<5; i++) {
    (function(){
        var i2 = i;
        result.push(function(){return i2});
    }());   
}

对象和构造函数

JavaScript 两种基础的面向对象机制:单一对象和构造函数。

单一对象

和所有值一样,对象也具有属性。你可以认为对象是一组属性的集合,事实也是如此,每个属性都是一个键值对。键名都是字符串,而值可以是 JavaScript 的任意值。

在 JavaScript 中,可以直接通过对象字面量去创建普通对象:

'use strict';
var jane = {
    name: 'Jane',
    describe: function() {
        return 'Person name '+ this.name;  
    }   
}

上述对象具有 name 和 describe 两个属性。你可以获取(get)以及设置(set)这些属性:

> jave.name // get
'Jane'
> jane.name = 'John' // set
> jane.newProperty = 'abc' // property create automatically

以函数作为值的属性被称为方法,如 describe。他们使用 this 对调用他们对象进行引用:

> jane.describe() // call method
'Person name John'
> jane.name = 'Jane'
> jane.describe()
'Person name Jane'

使用 in 运算符检查属性是否存在

> 'newProperty' in jane
true
> 'foo' in jane
false

如果读取一个不存在的属性,会得到 undefined。因此,之前的两个检查可以这样执行:

> jane.newProperty !== undefined
true
> jane.foo !== undefined
false

使用 delete 运算符移除属性

> delete jane.newProperty
true
> 'newProperty' in jane
false

任意属性名

属性的键名可以是任何字符串

> var obj = {'not an identifier': 123};
> obj['not an identifier']
123
> obj['not an identifier'] = 456;

方括号可以用来动态计算属性键名

> var obj = {hello: 'world'};
> var x = 'hello';
> obj[x]
'world'
> obj['hel'+'lo']
'world'

提取方法

如果对方法进去提取,则会失去与对象的连接。就这个函数而言,它不再是一个方法,this 的值也会是 undefined(在严格模式下).

'use strict';
var jane = {
    name: 'Jane',
    describe: function(){
        return 'Person name '+this.name;
    }
}

我们要从 jane 对象中提取 describe 方法,将它赋值给变量 func,然后对它进行调用。你会发现,他不能正常运行:

> var func = jane.describe;
> func()
TypeError: Cannot read property 'name' of undifined

处理这个问题的解决方案可以使用 bind() 方法,所有函数都支持。它会创建一个 this 总是指向给定值得新函数:

> var func2 = jane.describe.bind(jane);
> func2()
'Panson named Jane'

方法中的函数

所有函数都有其特殊的 this 变量。如果在方法中有嵌套函数,这可能会不太方便,因为在嵌套函数内部不能访问方法中的 this 变量。

var jane = {
    name: 'Jane',
    friends: ['Tarzen', 'Cheeta'],
    logHiToFriends: function(){
        'use strict';
        this.friends.forEach(function(friend){
            // this is undefined here
            console.log(this.name+' says hi to '+friend);
        });
    }
}

调用 logHiToFiends 会产生一个错误:

> jane.logHiToFriends()
TypeError: Cannot read property 'name' of undefined

让我们看看这个问题的两种解决方法。

  1. 第一种,我们可以将 this 保存在不同的变量中能够:
logHiTiFriends: function() {
    'use strict';
    var that = this;
    this.friends.forEach(function (friend) {
       console.log(that.name+' says hi to '+ friends); 
    }); 
}
  1. 利用 forEach 的第二个参数,它可以给 this 指定一个值:
logHiToFriends: function(){
    'use strict';
    this.friends.forEach(function(friend){
    console.log(this.name+ ' says hi to '+friend);
    }, this);
}

函数表达式在 JavaScript 中通常被当做函数调用中的参数来使用。在这些函数表达式中引用 this 时要特别小心。

构造函数:对象工厂

// 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);     
};

可以看到构造函数包含两部分。第一部分,Point 函数设置实例数据。第二部分,Point.prototype 属性包含一个带有方法的对象。第一部分里面的实例数据是特定于每一个实例的,而之后的方法数据是对所有实例共享的。

可以通过 new 运算符来使用 Point:

> var p = new Point(3, 4);
> p.x
3
> p.dist()
5

p 是 Point 的一个实例

> p instanceof Point
true

数组

数组是一些有序的元素,可以通过索引从0开始被访问。

数组字面量

数组字面量可以方便地创建数组元素:

> var arr = ['a', 'b', 'c'];

前面的数组有三个元素:字符串 a,b,c。你可以通过所以来访问它们:

> arr[0]
'a'
> arr[0] = 'x'
> arr
['x', 'b', 'c']

length 属性表明数组有多少元素。你可以通过使用它来添加或者删除元素:

> var arr = ['a', 'b'];
> arr.length
2
> arr[arr.length] = 'c';
> arr
['a', 'b', 'c']
> arr.length
3
> arr.length = 1
> arr
['a']

in 操作符也可以在数组中正常使用:

> var arr = ['a', 'b', 'c'];
> 1 in arr // is trher an element at index 1
true
> 5 in arr // is there an element at index 5
false

注意数组是对象,所以可以拥有对象属性

> var arr = [];
> arr.foo = 123;
> arr.foo
123

数组方法

数组拥有许多方法:

> 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 an element
1
> arr.indexof('y')
-1
> arr.join('-') // all ements in a single string
'x-b-c'
> arr.join('')
'xbc'
> arr.join()
'x,b,c'

遍历数组

forEach 迭代数组并且将当前的元素和元素的 index 扔到一个函数中:

['a', 'b', 'c'].forEach(function(elem, index){
    console.log(index + '. '+ elem) 
});

输出下面的内容:

0. a
1. b
2. c

map 通过应用一个函数映射到现有的数组的每个已经存在的元素创建一个新的数组:

> [1,2,3].map(function(x){return x*x})
[1, 4, 9]

正则表达式

JavaScript 内置的支持正则表达式。它们使用斜线分割:

/^abc$/
/[A-Za-z0-9]+/

test()方法:匹配吗

> /^a+b+$/.test('aabb')
true
> /^a+b+$/.test('aaa')
false

exec()方法:匹配以及捕获分组

> /a(b+)a/.exec('_abbba_aba_')
['abbba', 'bbb']

返回数组包含完整的匹配结果

replace()方法:搜索和替换

> '<a> <bbb>'.replace(/<(.*?)>/g, '[$1]')
'[a] [bbb]'

Math

Math 是一个包含运算功能的对象:

> Math.abs(-1)
1
> Math.pow(3, 2)
9
> Math.max(2, -1, 5)
5
> Math.round(1.9)
2
> Math.PI
3.141592653589793
> Math.cos(Math.PI)
-1
上一篇下一篇

猜你喜欢

热点阅读