JavaScript 入门(一)

最近,需要学习一下 React Native,所以先学习一下 JavaScript 作为知识储备。JavaScript 的学习主要分为 2~3 篇,这是第一篇,主要分为以下几个方面。



1. 简介

  • 1995年是由网景公司的 Brendan Eich 在两周之内设计出来的。

  • 因为网景开发了JavaScript,一年后微软又模仿 JavaScript 开发了 JScript,为了让 JavaScript 成为全球标准,几个公司联合 ECMA(European Computer Manufacturers Association)组织定制了JavaScript语言的标准,被称为 ECMAScript 标准

  • 简单说来就是,ECMAScript 是一种语言标准,而 JavaScript 是网景公司对 ECMAScript 标准的一种实现。

  • JavaScript 是在10天之内设计出来的,Bug 较多。由于JavaScript的标准——ECMAScript在不断发展,最新版ECMAScript 6标准(简称ES6)已经在2015年6月正式发布了,所以,讲到JavaScript的版本,实际上就是说它实现了ECMAScript标准的哪个版本

2. 基本语法

  • 在 JavaScript 中每个语句之后可以有 ;(分号) 也可以没有 ;(分号),如下两个语句都是正确的

    1
    2
    3
    4
    5
    // 正确,JavaScript 并不强制要求在每个语句的结尾加;,浏览器中负责执行 JavaScript 代码的引擎会自动在每个语句的结尾补上;。
    console.log("Hello World!")
    // 正确
    console.log("Hello World!");
  • 大小写敏感。JavaScript严格区分大小写,如果弄错了大小写,程序将报错或者运行不正常

3. 基本数据类型

  • Number:JavaScript不区分整数和浮点数,统一用Number表示,以下都是合法的Number类型

    1
    2
    3
    4
    5
    6
    123; // 整数123
    0.456; // 浮点数0.456
    1.2345e3; // 科学计数法表示1.2345x1000,等同于1234.5
    -99; // 负数
    NaN; // NaN表示Not a Number,当无法计算结果时用NaN表示
    Infinity; // Infinity表示无限大,当数值超过了JavaScript的Number所能表示的最大值时,就表示为Infinity
  • 字符串:字符串是以单引号’或双引号”括起来的任意文本,比如’abc’,”xyz”等等

  • 布尔值:JavaScript把null、undefined、0、NaN和空字符串’’视为false,其他值一概视为true
  • 比较运算符:
    • 在 JavaScript 中有两种比较运算符,分别是:
      1. 第一种是:== 它会自动转换数据类型再比较,很多时候,会得到非常诡异的结果;
      2. 第二种是:=== 它不会自动转换数据类型,如果数据类型不一致,返回false,如果一致,再比较
      3. 由于JavaScript这个设计缺陷,不要使用==比较,始终坚持使用===比较
    • 另一个例外是 NaN 这个特殊的 Number 与所有其他值都不相等,包括它自己:
      1. NaN === NaN; // false
      2. isNaN(NaN); // true,唯一能判断NaN的方法是通过isNaN()函数
    • 最后要注意浮点数的相等比较:
      1. 1 / 3 === (1 - 2 / 3); // false
      2. Math.abs(1 / 3 - (1 - 2 / 3)) < 0.0000001; // true 浮点数在运算过程中会产生误差,因为计算机无法精确表示无限循环小数。要比较两个浮点数是否相等,只能计算它们之差的绝对值,看是否小于某个阈值
  • null 和 undefined
    • null: null表示一个“空”的值,它和0以及空字符串’’不同,0是一个数值,’’表示长度为0的字符串,而null表示“空”
    • 大多数情况下,我们都应该用null。undefined仅仅在判断函数参数是否传递的情况下有用

4. 变量和常量

4.1 变量

  • 变量在JavaScript中就是用一个变量名表示,变量名是大小写英文、数字、$和_的组合,且不能用数字开头。变量名也可以用中文,但是,请不要给自己找麻烦。
  • 在JavaScript中,使用等号=对变量进行赋值。可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量,但是要注意只能用var申明一次,例如:

    1
    2
    var a = 123; // a的值是整数123
    a = 'ABC'; // a变为字符串
  • 这种变量本身类型不固定的语言称之为动态语言,与之对应的是静态语言。静态语言在定义变量时必须指定变量类型,如果赋值的时候类型不匹配,就会报错。例如Java是静态语言,赋值语句如下:

    1
    2
    int a = 123; // a是整数类型变量,类型用int申明
    a = "ABC"; // 错误:不能把字符串赋给整型变量

4.2 常量

  • 在 ES6 标准中引入了关键字 const 定义常量,const 与 let 都具有块级作用域

    1
    2
    3
    4
    5
    'use strict';
    const PI = 3.14;
    PI = 3; // 某些浏览器不报错,但是无效果!
    PI; // 3.14

5. 数组和对象

5.1 数组

  • JavaScript 的数组可以包括任意数据类型,如下所示:

    1
    2
    var arrays = [1, 2, 3.14, 'Hello', null, true];
    var arrays1 = new Array(1, 2, 3); // 创建了数组[1, 2, 3]
  • 数组的元素可以通过索引来访问。请注意,索引的起始值为0:

    1
    2
    3
    4
    var arr = [1, 2, 3.14, 'Hello', null, true];
    arr[0]; // 返回索引为0的元素,即1
    arr[5]; // 返回索引为5的元素,即true
    arr[6]; // 索引超出了范围,返回undefined
  • 也可以通过数组的索引改变该位置的值

    1
    2
    3
    var arr1 = ["A", "B", "C"];
    arr1[0] = 99;
    arr1[4] = 4;
  • 可以动态地改变数组的大小

    1
    2
    3
    4
    var arr = [1, 2, 3];
    arr.length;
    arr.length = 6;
    arr.length = 2;
  • 在 JavaScript 中也提供了许多操纵数组的方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    var arr2 = [10, 20, '30', 'xyz'];
    // indexOf 方法
    arr.indexOf(10); // 元素10的索引为0
    arr.indexOf(30); // 元素30没有找到,返回-1
    arr.indexOf('30'); // 元素'30'的索引为2
    // slice()就是对应String的substring()版本,它截取Array的部分元素,然后返回一个新的Array
    var arr3 = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
    arr3.slice(0, 3); // 从索引0开始,到索引3结束,但不包括索引3: ['A', 'B', 'C']
    arr3.slice(3); // 从索引3开始到结束:['D', 'E', 'F', 'G']
    var arr4 = arr3.slice();
    arr4 === arr3;
    // push()向Array的末尾添加若干元素,pop()则把Array的最后一个元素删除掉:
    var arr5 = [1, 2];
    arr5.push('A', 'B');
    arr5.pop();
    arr5.pop();
    arr5.pop();
    arr5.pop();
    arr5.pop();
    // 如果要往Array的头部添加若干元素,使用unshift()方法,shift()方法则把Array的第一个元素删掉:
    var arr6 = [1,2 ];
    arr6.unshift('A', 'B');
    arr.shift();
    arr.shift();
    arr.shift();
    arr.shift();
    arr.shift();
    // sort()可以对当前Array进行排序,它会直接修改当前Array的元素位置,直接调用时,按照默认顺序排序:
    var arr7 = ['B', 'C', 'A'];
    arr7.sort();
    arr7; // ['A', 'B', 'C']
    // reverse() 方法将整个 Array 中的元素反转
    var arr8 = [1, 2, 3];
    arr8.reverse();
    arr8;
    // splice() 方法是修改数组的方法,具体用法如下
    var arr9 = [1, 2, 3];
    arr9.splice(1, 2, "js", "java", "kotlin");
    arr9.splice(2, 2);
    arr9.splice(2, 0, "python", "php", "c++");
    // concat() 方法将当前 Array 和另一个 Array 连接起来,并返回一个新的 Array
    arr10 = [1, 2, 3];
    var arr11 = arr10.concat([4, 5, 6]);
    var arr12 = arr10.concat(4, 5, 6, [7, 8, 9]);
    // join 方法是将当前数组中的元素用指定的字符串连接起来,然后返回连接后的字符串
    var arr13 = ['A', 'B', 'C', 1, 2, 3];
    var temp = arr13.join('-'); // temp 的值是 ‘A-B-C-1-2-3’
    // 多维数组
    var arr14 = [[1, 2, 3], [400, 500, 600], ['A', 'B', 'C']];
    var char1 = arr14[1][1]; // char1 == 500
    var char2 = arr14[2][1]; // char2 == 'A'

5.2 对象

  • JavaScript 的对象是一组由 键-值 组成的无序集合,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var lijiankun24 = {
    name: 'lijiankun24',
    age: 99,
    company: 'meituan',
    score: null
    };
    var name = lijiankun24.name;
    var age = lijiankun24.age;
    var company = lijiankun24[company];
    var company1 = lijiankun24['company'];

    JavaScript 用一个 {…} 表示一个对象,键值对以 xxx: xxx 形式申明,用 , 隔开。注意,最后一个键值对不需要在末尾加 ,,如果加了,有的浏览器(如低版本的IE)将报错

  • JavaScript 对象的键都是字符串类型,值可以是任意数据类型

    1
    2
    3
    // 要获取一个对象的属性,我们用 `对象变量.属性名` 的方式:
    person.name; // 'Bob'
    person.zipcode; // null
  • 由于 JavaScript 的对象是动态类型的,可以给对象动态地添加属性或删除属性

    1
    2
    3
    4
    5
    6
    var xiaoming = {
    name: 'xiaoming'
    };
    xiaoming.age = 18;
    delete xiaoming.name;
    xiaoming.school = 'HighSchool';
  • 可以通过 in 判断一个属性是否存在

    1
    2
    3
    var isAgeExist = 'age' in xiaoming;
    var isNameExist = 'name' in xiaoming;
    var isToStringExist = 'toString' in xiaoming;
  • 要判断一个属性是否是对象自身拥有的属性,而不是通过继承得到的,可以用 ‘hasOwnProperty()’ 方法判断

    1
    2
    3
    4
    5
    6
    var xiaohong = {
    name: 'xiaohong',
    age: 18
    }
    xiaohong.hasOwnProperty('name'); // ture
    xiaohong.hasOwnProperty('toString'); // false

6. 变量作用域

6.1 var 的作用域

  • 如果在一个函数中使用 var 关键字申明了一个变量,则该变量的作用域是该函数体内,在函数体外不可引用该变量

    1
    2
    3
    4
    5
    6
    7
    8
    'use strict';
    function foo() {
    var x = 1;
    x = x + 1;
    }
    x = x + 2; // ReferenceError! 无法在函数体外引用变量x
  • 在 JavaScript 中,函数是可以嵌套的,那在嵌套的函数中,两个函数的变量的作用域是什么样的呢?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    'use strict';
    function foo() {
    var x = 1;
    function bar() {
    var y = x + 1; // bar可以访问foo的变量x!
    }
    var z = y + 1; // ReferenceError! foo不可以访问bar的变量y!
    }

    在嵌套函数中,外部函数可以使用内部函数的变量,但是内部函数不可以使用外部函数的变量

  • 在嵌套函数中,如果外部函数和内部函数使用了同样的相同名称的变量,那怎么办呢?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    'use strict';
    function foo() {
    var x = 1;
    function bar() {
    var x = 'A';
    console.log('x in bar() = ' + x); // 'A'
    }
    console.log('x in foo() = ' + x); // 1
    bar();
    }
    foo();

    JavaScript的函数在查找变量时从自身函数定义开始,从“内”向“外”查找。如果内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量。

6.2 全局作用域

  • 不在任何函数体内定义的变量就具有全局作用域。实际上,JavaScript 默认有一个全局对象 window,全局作用域的变量实际上被绑定到 window 的一个属性

    1
    2
    3
    4
    5
    6
    'use strict';
    // 直接访问全局变量course和访问window.course是完全一样的
    var course = 'Learn JavaScript';
    alert(course); // 'Learn JavaScript'
    alert(window.course); // 'Learn JavaScript'
  • 实际上,函数也被视为一个全局变量,被绑定到 window 对象上

    1
    2
    3
    4
    5
    6
    7
    8
    'use strict';
    function foo() {
    alert('foo');
    }
    foo(); // 直接调用foo()
    window.foo(); // 通过window.foo()调用
  • 任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续往上查找,最后如果在全局作用域中也没有找到,则报ReferenceError错误

6.3 局部作用域

  • 由于 JavaScript 的变量作用域实际上是函数内部,我们在 for 循环等语句块中是无法定义具有局部作用域的变量的

    1
    2
    3
    4
    5
    6
    7
    8
    'use strict';
    function foo() {
    for (var i=0; i<100; i++) {
    //
    }
    i += 100; // 仍然可以引用变量i
    }
  • 为了解决块级作用域,ES6 引入了新的关键字 let,用 let 替代 var 可以申明一个块级作用域的变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    'use strict';
    function foo() {
    var sum = 0;
    for (let i=0; i<100; i++) {
    sum += i;
    }
    // SyntaxError:
    i += 1;
    }

6.4 变量提升

  • 在执行 JavaScript 的时候,JavaScript 引擎会自动将函数体中申明的所有变量 “提升” 到函数顶部

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    'use strict';
    function foo() {
    var x = 'Hello, ' + y;
    console.log(x);
    var y = 'Bob';
    }
    foo();
    // 此函数执行时并不会报错,其执行结果是:‘Hello, undefined’

    上述代码,在执行时,JavaScript 执行引擎看到的代码相当于

    1
    2
    3
    4
    5
    6
    7
    function foo() {
    var y; // 提升变量 y 的申明,此时 y 为 undefined,
    var x = 'Hello, ' + y;
    console.log(x);
    y = 'Bob';
    }
    // 注意:JavaScript 引擎只会提升变量的申明,并不会提升变量的赋值

    由于 JavaScript 的这一怪异的 “特性”,我们在函数内部定义变量时,请严格遵守“在函数内部首先申明所有的变量”这一规则。最常见的做法是用一个 var 申明函数内部用到的所有变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function foo() {
    var
    x = 1, // x初始化为1
    y = x + 1, // y初始化为2
    z, i; // z和i为undefined
    // 其他语句:
    for (i=0; i<100; i++) {
    ...
    }
    }

6.5 名字空间

  • 在 JavaScript 中全局变量都会绑定到 window 上,不同的 JavaScript 文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突

  • 减少冲突的一个常见的做法就是:把自己所有的变量和函数全部绑定到一个全局变量中,如下所示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 唯一的全局变量MYAPP:
    var MYAPP = {};
    // 其他变量:
    MYAPP.name = 'myapp';
    MYAPP.version = 1.0;
    // 其他函数:
    MYAPP.foo = function () {
    return 'foo';
    };
  • 把自己的代码全部放入唯一的名字空间MYAPP中,会大大减少全局变量冲突的可能。许多著名的JavaScript库都是这么干的:jQuery,YUI,underscore等等。

7. 集合

7.1 Map

  • 在最新的 ES6 规范中引入了新的数据类型 Map,在 Map 中可以以键值对的形式存储数据,具有极快的查找速度
    1
    2
    3
    4
    5
    6
    7
    8
    9
    var map = new Map([['Michael', 95], ['Bob', 80], ['Lee', 90]]);
    var map1 = new Map();
    map1.set(100, 1000);
    map1.set(200, 2000);
    map1.has(100);
    map1.get(200);
    map1.delete(200);
    map1.set(100, 100); // 由于一个 key 只能对应一个 value,所以多次对一个 key 存入 value 的话,后面赋的值会把前面赋的值覆盖

7.2 Set

  • Set 也是最新的 ES6 规范中引入的,和 Map 类似,也是一组 key 的集合,但不存储 value,由于 key 不能重复,所以在 Set 中,没有重复的 key
    1
    2
    3
    4
    5
    6
    var set = new Set([1, 2, 3, 4, 5]);
    var set1 = new Set();
    set1.add(1);
    set1.add(2);
    set1.delete(1);

7.3 循环

  • JavaScript 中的普通循环和 Java 中的循环一样,如下所示:

    1
    2
    3
    4
    5
    6
    var arr = ['A', 'B', 'C', 'D'];
    var i, x;
    x = arr.length;
    for(i = 0; i < x; i++){
    console.log(arr[i]);
    }
  • for … in 循环:for … in 循环可以把一个对象的所有属性依次循环遍历

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var lijiankun24 = {
    name: 'lijiankun24',
    age: 99,
    company: 'meituan'
    };
    for(var key in lijiankun24){
    console.log(key + '=' + lijiankun[key]);
    }
    var array = ['A', 'B', 'C'];
    for(var index in array){
    console.log(index); // '0', '1', '2'
    console.log(array[index]); // 'A', 'B', 'C'
    }
    // 请注意,for ... in对Array的循环得到的是String而不是Number

7.4 iterable

  • iterable 是 ES6 规范中引入的,Array、Map 和 Set 都属于 iterable 类型
  • for … of 循环。iterable 类型的集合可以通过新的 for … of 方便的循环遍历

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var array = ['A', 'B', 'C'];
    for(var x of array){
    console.log(x);
    }
    var set = new Set(['D', 'E', 'F']);
    for(var x of set){
    console.log(x);
    }
    var map = new Map([['1', 'A'], ['2', 'B'], ['3', 'C']]);
    for(var x of map){
    console.log(x[0] + '=' + x[1]);
    }
  • forEach 循环。iterable 类型的集合可以直接使用 iterable 内置的 forEach 循环。注意:forEach() 循环遍历是在 ES5.1 规范中引入的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    'use strict';
    /**
    * forEach 循环接受一个函数,遍历每一项的时候都会调用该函数
    */
    var array = [1, 2, 3, 4];
    array.forEach(function(element, index, array){
    // element:指向当前元素的值
    // index:指向当前索引
    // array:指向 Array 对象本身
    console.log(element + ', index = ' + index);
    });
    /**
    * 由于在 Set 中没有索引,所以 forEach function 的前两个值相等
    */
    var set = new Set([1, 2, 3, 4]);
    set.forEach(function(value, sameValue, set){
    // value === sameValue
    console.log(value);
    });
    var map = new Map([[1, 'A'], [2, 'B'], [3, 'C']]);
    map.forEach(function(value, key, map){
    console.log(key + ' = ' + value);
    });
    // 由于 JavaScript 的函数调用不要求参数必须一致,因此可以忽略它们
    var array1 = [1, 2, 3, 4];
    array1.forEach(function(value){
    console.log('value = ' + value);
    });

参考资料:

JavaScript教程