JavaScrpit 概念快速概览

本贴最后更新于 1185 天前,其中的信息可能已经斗转星移

快速入门

javaScript 每个语句以 ; 结束,语句块用 {...}

注释

// 开头直到行末的字符被视为行注释,块注释是用 /*...*/ 把多行字符包裹起来,把一大“块”视为一个注释。

大小写

JavaScript 严格区分大小写,如果弄错了大小写,程序将报错或者运行不正常。

类型

  1. number(不区分整数和浮点数)1.23e3 =1230 ;NaN 无法计算结果时(not a Num);Infinity 无限大;
    NaN === NaN; // false 唯一能判断NaN的方法是通过isNaN()函数:
    1 / 3 === (1 - 2 / 3); // false
    Math.abs(1 / 3 - (1 - 2 / 3)) < 0.0000001; // true
    这不是JavaScript的设计缺陷。浮点数在运算过程中会产生误差,因为计算机无法精确表示无限循环小数。要比较两个浮点数是否相等,只能计算它们之差的绝对值,看是否小于某个阈值
    
  2. string
    `这是一个
    多行
    字符串`;
    // ES6新增了一种模板字符串
    var message = `你好, ${name}, 你今年${age}岁了!`;
    对字符串的某个索引赋值,不会有任何错误,但是,也没有任何效果.
    var s = 'Test';
    s[0] = 'X';
    alert(s); // s仍然为'Test'
    
  3. boolean
  4. nullundefined。 用 null 表示一个空的值,而 undefined 表示值未定义。大多数情况下,我们都应该用 nullundefined 仅仅在判断函数参数是否传递的情况下有用。
  5. 数组。直接给 Arraylength 赋一个新的值会导致 Array 大小的变化,如果通过索引赋值时,索引超过了范围,同样会引起 Array 大小的变化
  6. 对象。一组由键-值组成的无序集合。不是一个有效的变量,就需要用 '' 括起来。访问这个属性也无法使用 . 操作符,必须用 ['xxx'] 来访问。检测 xiaoming 是否拥有某一属性,可以用 in 操作符,这个属性不一定是 xiaoming 的,它可能是 xiaoming 继承得到的,要判断一个属性是否是 xiaoming 自身拥有的,而不是继承得到的,可以用 hasOwnProperty() 方法。对象有个小问题,就是键必须是字符串。
  7. 变量。变量名是大小写英文、数字、$_ 的组合,且不能用数字开头。变量名也不能是 JavaScript 的关键字,如 ifwhile 等。变量本身类型不固定的语言称之为动态语言。JavaScript 的函数可以嵌套,此时,内部函数可以访问外部函数定义的变量,反过来则不行。

strict 模式

非严格模式:如果一个变量没有通过 var 申明就被使用,那么该变量就自动被申明为全局变量。在同一个页面的不同的 JavaScript 文件中,如果都不用 var 申明,恰好都使用了变量 i,将造成变量 i 互相影响,产生难以调试的错误结果。

使用 var 申明的变量则不是全局变量,它的范围被限制在该变量被申明的函数体内(函数的概念将稍后讲解),同名变量在不同的函数体内互不冲突

ECMA 在后续规范中推出了 strict 模式

启用 strict 模式的方法是在 JavaScript 代码的第一行写上:

'use strict';

在 strict 模式下运行的 JavaScript 代码,强制通过 var 申明变量,未使用 var 申明变量就使用的,将导致运行错误。

Map

Map 是一组键值对的结构,具有极快的查找速度。

var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]); // 初始化Map需要一个二维数组,或者直接初始化一个空Map
m.get('Michael'); // 95

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

由于一个 key 只能对应一个 value,所以,多次对一个 key 放入 value,后面的值会把前面的值冲掉:

Set

是一组 key 的集合,但不存储 value。由于 key 不能重复,所以,在 Set 中,没有重复的 key。重复元素在 Set 中自动被过滤。

创建一个Set,需要提供一个Array作为输入,或者直接创建一个空Set:
var s1 = new Set(); // 空Set
var s2 = new Set([1, 2, 3]); // 含1, 2, 3
s; // Set {1, 2, 3, 4}
s.add(4);
s.delete(3);

iterable

具有 iterable 类型的集合可以通过新的 for ... of 循环来遍历。

ES6 标准引入了新的 iterable 类型,ArrayMapSet 都属于 iterable 类型。

for ... in 遍历的实际上是对象的属性名称。 一个 Array 数组实际上也是一个对象,它的每个元素的索引被视为一个属性。(但 Arraylength 属性却不包括在内)

for ... of 循环则完全修复了这些问题,它只循环集合本身的元素。

直接使用 iterable 内置的 forEach 方法,它接收一个函数,每次迭代就自动回调该函数。(但是 forEach 需要注意里面不能有异步操作)。

SetArray 类似,但 Set 没有索引,因此回调函数的前两个参数都是元素本身:

var s = new Set(['A', 'B', 'C']);
s.forEach(function (element, sameElement, set) {
    console.log(element);
});

Map 的回调函数参数依次为 valuekeymap 本身:

var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
m.forEach(function (value, key, map) {
    console.log(value);
});

函数

如果没有 return 语句,函数执行完毕后也会返回结果,只是结果为 undefined

传入参数

JavaScript 还有一个免费赠送的关键字 arguments,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments 类似 Array 但它不是一个 Array,利用 arguments,你可以获得调用者传入的所有参数。也就是说,即使函数不定义任何参数,还是可以拿到参数的值,arguments 最常用于判断传入参数的个数。

ES6 标准引入了 rest 参数,上面的函数可以改写为:

function foo(a, b, ...rest) {
    console.log('a = ' + a);
    console.log('b = ' + b);
    console.log(rest);
}

foo(1, 2, 3, 4, 5);
// 结果:
// a = 1
// b = 2
// Array [ 3, 4, 5 ]

变量提升

var 声明的变量会有变量提升,但是 const 和 let 没有。变量提升首先是 udefined,只有在赋值语句的位置才会赋值。
当使用一个没有赋值的变量时,是 undefined,使用没被声明的变量会直接报错。

名字空间

全局作用域:window。基本所有的都挂在 window 下。

不同的 JavaScript 文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中

局部作用域

块级作用域的变量:let 和 const

解构

let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
let [, , z] = ['hello', 'JavaScript', 'ES6']; // 忽略前两个元素,只对z赋值第三个元素
var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school',
    address: {
        city: 'Beijing',
        street: 'No.1 Road',
        zipcode: '100001'
    }
};
var {name, address: {city, zip}} = person;
name; // '小明'
city; // 'Beijing'
zip; // undefined, 因为属性名是zipcode而不是zip
// 注意: address不是变量,而是为了让city和zip获得嵌套的address对象的属性:
address; // Uncaught ReferenceError: address is not defined

var {name, single=true} = person;
// 声明变量:
var x, y;
// 解构赋值:
{x, y} = { name: '小明', x: 100, y: 200};
// 语法错误: Uncaught SyntaxError: Unexpected token =
这是因为JavaScript引擎把{开头的语句当作了块处理,于是=不再合法。解决方法是用小括号括起来:

({x, y} = { name: '小明', x: 100, y: 200});

交换变量

var x=1, y=2;
[x, y] = [y, x]

如果一个函数接收一个对象作为参数,那么,可以使用解构直接把对象的属性绑定到变量中,使用解构赋值可以减少代码量。

方法

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: function () {
        var y = new Date().getFullYear();
        return y - this.birth;
    }
};

xiaoming.age; // function xiaoming.age()
xiaoming.age(); // 今年调用是25,明年调用就变成26了

function getAge() {
    var y = new Date().getFullYear();
    return y - this.birth;
}

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: getAge
};

xiaoming.age(); // 25, 正常结果
getAge(); // NaN

在一个独立的函数调用中,根据是否是 strict 模式,this 指向 undefinedwindow,不过,我们还是可以控制 this 的指向的。

函数本身的 apply 方法,它接收两个参数,第一个参数就是需要绑定的 this 变量,第二个参数是 Array,表示函数本身的参数。call() 把参数按顺序传入。对普通函数调用,我们通常把 this 绑定为 null

装饰器

利用 apply(),我们还可以动态改变函数的行为。JavaScript 的所有对象都是动态的,即使内置的函数,我们也可以重新指向新的函数。

'use strict';

var count = 0;
var oldParseInt = parseInt; // 保存原函数

window.parseInt = function () {
    count += 1;
    return oldParseInt.apply(null, arguments); // 调用原函数
};

高阶函数

一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。

map/reduce

map() 方法定义在 JavaScript 的 Array 中,返回一个新的函数

Array 的 reduce() 把一个函数作用在这个 Array[x1, x2, x3...] 上,这个函数必须接收两个参数,reduce() 把结果继续和序列的下一个元素做累积计算

filter

Array 的某些元素过滤掉,然后返回剩下的元素。根据返回值是 true 还是 false 决定保留还是丢弃该元素。

Sort

对于两个元素 xy,如果认为 x < y,则返回 -1,如果认为 x == y,则返回 0,如果认为 x > y,则返回 1,这样,排序算法就不用关心具体的比较过程,而是根据比较结果直接排序。

字符串根据 ASCII 码进行排序,而小写字母 a 的 ASCII 码在大写字母之后。sort() 方法会直接对 Array 进行修改,它返回的结果仍是当前 Array

Array 的方法

every 每个都满足 返回 true ;some 存在满足条件的 返回 true;find ,查找符合条件的第一个元素,如果找到了,返回这个元素,否则,返回 undefined;findIndex, findIndex()find() 类似,也是查找符合条件的第一个元素,不同之处在于 findIndex() 会返回这个元素的索引,如果没有找到,返回 -1 ;forEach ,forEach()map() 类似,它也把每个元素依次作用于传入的函数,但不会返回新的数组。

闭包

内部函数可以引用外部函数的参数和局部变量。

当一个函数返回了一个函数后,其内部的局部变量还被新函数引用

返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变

function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push(function () {
            return i * i;
        });
    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

f1(); // 16
f2(); // 16
f3(); // 16


function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push((function (n) {
            return function () {
                return n * n;
            }
        })(i));
    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

f1(); // 1
f2(); // 4
f3(); // 9

箭头函数

ES6 标准新增了一种新的函数:Arrow Function(箭头函数)。

但实际上,箭头函数和匿名函数有个明显的区别:箭头函数内部的 this 是词法作用域,由上下文确定。所以,用 call() 或者 apply() 调用箭头函数时,无法对 this 进行绑定,即传入的第一个参数被忽略。

generator

generator(生成器)是 ES6 标准引入的新的数据类型。一个 generator 看上去像一个函数,但可以返回多次。

generator 和函数不同的是,generator 由 function* 定义(注意多出的 * 号),并且,除了 return 语句,还可以用 yield 返回多次。

函数在执行过程中,如果没有遇到 return 语句(函数末尾如果没有 return,就是隐含的 return undefined;),控制权无法交回被调用的代码。

直接调用一个 generator 和调用函数不一样,仅仅是创建了一个 generator 对象,还没有去执行它。每次遇到 yield x; 就返回一个对象 {value: x, done: true/false},然后“暂停”。如果 donetrue,则 value 就是 return 的返回值。

直接用 for ... of 循环迭代 generator 对象,这种方式不需要我们自己判断 done


'use strict'

function* fib(max) {
    var
        t,
        a = 0,
        b = 1,
        n = 0;
    while (n < max) {
        yield a;
        [a, b] = [b, a + b];
        n ++;
    }
    return;
}

for (var x of fib(10)) {
    console.log(x); // 依次输出0, 1, 1, 2, 3, ...
}

generator 和普通函数相比,可以在执行过程中多次返回,所以它看上去就像一个可以记住执行状态的函数,利用这一点,写一个 generator 就可以实现需要用面向对象才能实现的功能。

generator 还有另一个巨大的好处,就是把异步回调代码变成“同步”代码。

try {
    r1 = yield ajax('http://url-1', data1);
    r2 = yield ajax('http://url-2', data2);
    r3 = yield ajax('http://url-3', data3);
    success(r3);
}
catch (err) {
    handle(err);
}

标准对象

typeof 123; // 'number'
typeof NaN; // 'number'
typeof 'str'; // 'string'
typeof true; // 'boolean'
typeof undefined; // 'undefined'
typeof Math.abs; // 'function'
typeof null; // 'object'
typeof []; // 'object'
typeof {}; // 'object'

包装对象

var n = new Number(123); // 123,生成了新的包装类型
var b = new Boolean(true); // true,生成了新的包装类型
var s = new String('str'); // 'str',生成了新的包装类型

typeof new Number(123); // 'object'
new Number(123) === 123; // false

typeof new Boolean(true); // 'object'
new Boolean(true) === true; // false

typeof new String('str'); // 'object'
new String('str') === 'str'; // false

此时,Number()BooleanString() 被当做普通函数,把任何类型的数据转换为 numberbooleanstring 类型(注意不是其包装类型).

var n = Number('123'); // 123,相当于parseInt()或parseFloat()
typeof n; // 'number'

var b = Boolean('true'); // true
typeof b; // 'boolean'

var b2 = Boolean('false'); // true! 'false'字符串转换结果为true!因为它是非空字符串!
var b3 = Boolean(''); // false

var s = String(123.45); // '123.45'
typeof s; // 'string'

规则

  • 不要使用 new Number()new Boolean()new String() 创建包装对象
  • parseInt()parseFloat() 来转换任意类型到 number
  • String() 来转换任意类型到 string,或者直接调用某个对象的 toString() 方法
  • 通常不必把任意类型转换为 boolean 再判断,因为可以直接写 if (myVar) {...}
  • typeof 操作符可以判断出 numberbooleanstringfunctionundefined
  • 判断 Array 要使用 Array.isArray(arr)
  • 判断 null 请使用 myVar === null
  • 判断某个全局变量是否存在用 typeof window.myVar === 'undefined'
  • 函数内部判断某个变量是否存在用 typeof myVar === 'undefined'

任何对象都有 toString() 方法吗?nullundefined 就没有!

number 对象调用 toString() 报 SyntaxError

123.toString(); // SyntaxError
123..toString(); // '123', 注意是两个点!
(123).toString(); // '123'

Data

Date 对象用来表示日期和时间。

var now = new Date(); // 获取系统当前时间
now; // Wed Jun 24 2015 19:49:22 GMT+0800 (CST)
now.getFullYear(); // 2015, 年份
now.getMonth(); // 5, 月份,注意月份范围是0~11,5表示六月
now.getDate(); // 24, 表示24号
now.getDay(); // 3, 表示星期三
now.getHours(); // 19, 24小时制
now.getMinutes(); // 49, 分钟
now.getSeconds(); // 22, 秒
now.getMilliseconds(); // 875, 毫秒数
now.getTime(); // 1435146562875, 以number形式表示的时间戳

当前时间是浏览器从本机操作系统获取的时间,所以不一定准确,因为用户可以把当前时间设定为任何值。

var d = new Date(2015, 5, 19, 20, 15, 30, 123);
d; // Fri Jun 19 2015 20:15:30 GMT+0800 (CST)

var d = Date.parse('2015-06-24T19:49:22.875+08:00');
d; // 1435146562875

var d = new Date(1435146562875);
d; // Wed Jun 24 2015 19:49:22 GMT+0800 (CST)
d.getMonth(); // 5

Date 对象表示的时间总是按浏览器所在时区显示的,不过我们既可以显示本地时间,也可以显示调整后的 UTC 时间

var d = new Date(1435146562875);
d.toLocaleString(); // '2015/6/24 下午7:49:22',本地时间(北京时区+8:00),显示的字符串与操作系统设定的格式有关
d.toUTCString(); // 'Wed, 24 Jun 2015 11:49:22 GMT',UTC时间,与本地时间相差8小时

RegExp

正则表达式是一种用来匹配字符串的强有力的武器。它的设计思想是用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符串,我们就认为它“匹配”了,否则,该字符串就是不合法的。

var re = /^\d{3}\-\d{3,8}$/;
re.test('010-12345'); // true

'a b   c'.split(/\s+/); // ['a', 'b', 'c']

正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。

var re = /^(\d+)(0*)$/;
re.exec('102300'); // ['102300', '102300', '']

加个 ? 就可以让 \d+ 采用非贪婪匹配

var re = /^(\d+?)(0*)$/;
re.exec('102300'); // ['102300', '1023', '00']

g,表示全局匹配

var r1 = /test/g;
// 等价于:
var r2 = new RegExp('test', 'g');

全局匹配可以多次执行 exec() 方法来搜索一个匹配的字符串。当我们指定 g 标志后,每次运行 exec(),正则表达式本身会更新 lastIndex 属性,表示上次匹配到的最后索引

var s = 'JavaScript, VBScript, JScript and ECMAScript';
var re=/[a-zA-Z]+Script/g;

// 使用全局匹配:
re.exec(s); // ['JavaScript']
re.lastIndex; // 10

re.exec(s); // ['VBScript']
re.lastIndex; // 20

re.exec(s); // ['JScript']
re.lastIndex; // 29

re.exec(s); // ['ECMAScript']
re.lastIndex; // 44

re.exec(s); // null,直到结束仍没有匹配到

JSON

JSON 是 JavaScript Object Notation 的缩写,它是一种数据交换格式。

在 JSON 中,一共就这么几种数据类型:

  • number:和 JavaScript 的 number 完全一致;
  • boolean:就是 JavaScript 的 truefalse
  • string:就是 JavaScript 的 string
  • null:就是 JavaScript 的 null
  • array:就是 JavaScript 的 Array 表示方式——[]
  • object:就是 JavaScript 的 { ... } 表示方式。

JSON 还定死了字符集必须是 UTF-8

JSON 的字符串规定必须用双引号 "",Object 的键也必须用双引号 ""

  • JavaScript

    JavaScript 一种动态类型、弱类型、基于原型的直译式脚本语言,内置支持类型。它的解释器被称为 JavaScript 引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在 HTML 网页上使用,用来给 HTML 网页增加动态功能。

    729 引用 • 1327 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...