通用技术 js 常用 -- 你懂的

在路上 · 2020年06月17日 · 最后由 在路上 回复于 2020年06月18日 · 1787 次阅读

全篇来自廖雪峰老师的教程,本篇近作为笔记使用。

1、== 和===的区别

false == 0;    // true
false === 0;  // false

奇怪吧?因为:这属于 javascript 的一个缺陷

  • == 会自动转换数据类型再比较,很多时候,会得到非常诡异的结果;
  • === 不会自动转换数据类型,如果数据类型不一致,返回 false,如果一致,再比较;

例外情况:
(1) NaN

NaN === Nan;  // false

唯一能判断 NaN 的方法是通过 isNaN() 函数:

isNaN(NAN);  // true

2、null 和 undefined**

null表示一个 “空” 的值,它和0以及空字符串' '不通,0是一个数值,' '表示长度为0的字符串,而null表示 “空”。

3、变量

(1)命名
变量名是大小写英文、数字、$ 和_的组合,且不能用数字开头
注意:变量名也可以用中文,但是,请不要给自己找麻烦。
(2)变量定义
Javascript 定义变量:

var a = 123;  // a的值是整数123
a = 'ABC';  // a变为字符串

python 也是动态语言:

a = 123
a = "ABC"

Java 是静态语言,定义变量时必须指定变量类型:

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

4、数组

有一种数组长度说改就改:

// 直接给Array的length赋一个新的值会导致Array大小的变化:
var arr = [1, 2, 3];
arr.length; // 3
arr.length = 6;
arr; // arr变为[1, 2, 3, undefined, undefined, undefined]
arr.length = 2;
arr; // arr变为[1, 2]

// 如果通过索引赋值时,索引超过了范围,同样会引起Array大小的变化:
var arr = [1, 2, 3];
arr[5] = 'x';
arr; // arr变为[1, 2, 3, undefined, undefined, 'x']

数组常用函数:
(1)索引:通过 indexOf() 来搜索一个指定元素的位置

var arr = [10, 20, '30', 'xyz'];
arr.indexOf(10);  // 元素10的索引为0

(2)截取:slice() 可以截取 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']

(3)如果不给 slice() 传递任何参数,它就会从头到尾截取所有元素。利用这一点,我们可以很容易地复制一个 Array:

var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
var aCopy = arr.slice();

(4)末尾添加和删除任意元素:push() 向 Array 的末尾添加若干元素,pop() 则把 Array 的最后一个元素删除掉:

var arr = [1, 2];
arr.push('A', 'B'); // 返回Array新的长度: 4
arr; // [1, 2, 'A', 'B']
arr.pop(); // pop()返回'B'
arr.pop(); // 空数组继续pop不会报错,而是返回undefined

(5)头部添加和头部删除:如果要往 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(); // 空数组继续shift不会报错,而是返回undefined

(6)排序: sort() 可以对当前 Array 进行排序,它会直接修改当前 Array 的元素位置,直接调用时,按照默认顺序排序:

var arr = ['B', 'C', 'A'];
arr.sort();
arr; // ['A', 'B', 'C']

(7)反转: reverse() 把整个 Array 的元素给掉个个,也就是反转

var arr = ['one', 'two', 'three'];
arr.reverse(); 
arr; // ['three', 'two', 'one']

(8)万能方法: 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']

(9)连接: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']

请注意,concat() 方法并没有修改当前 Array,而是返回了一个新的 Array。

(10)join:join() 方法是一个非常实用的方法,它把当前 Array 的每个元素都用指定的字符串连接起来,然后返回连接后的字符串

var arr = ['A', 'B', 'C', 1, 2, 3];
arr.join('-'); // 'A-B-C-1-2-3'

(11)多维数组:
如果数组的某个元素又是一个 Array,则可以形成多维数组,例如:

var arr = [[1, 2, 3], [400, 500, 600], '-'];

5、对象

要判断一个属性是否是对象自身拥有的,而不是继承得到的,可以用 hasOwnProperty() 方法。不要用 in,继承得到的属性也会返回 true;

var xiaoming = {
    name: '小明'
};
xiaoming.hasOwnProperty('name'); // true
xiaoming.hasOwnProperty('toString'); // false

6、条件判断

JavaScript 把 null、undefined、0、NaN 和空字符串' '视为 false,其他值一概视为 true.

7、iterable

for...in 循环问题:遍历的实际上是对象的属性名称
for...of,遍历的是对象的元素

更好的遍历方式:

// Array
var a = ['A', 'B', 'C'];
a.forEach(function (element, index, array) {
    // element: 指向当前元素的值
    // index: 指向当前索引
    // array: 指向Array对象本身
    console.log(element + ', index = ' + index);
});


// Set:Set与Array类似,但Set没有索引,因此回调函数的前两个参数都是元素本身:
var s = new Set(['A', 'B', 'C']);
s.forEach(function (element, sameElement, set) {
    console.log(element);
});

// Map: Map的回调函数参数依次为value、key和map本身
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
m.forEach(function (value, key, map) {
    console.log(value);
});

// 如果只对某些参数感兴趣,可以忽略其他参数
var a = ['A', 'B', 'C'];
a.forEach(function (element) {
    console.log(element);
});

8、函数

(1)函数多传参或少传参问题

由于 JavaScript 允许传入任意个参数而不影响调用:

  • 因此传入的参数比定义的参数多也没有问题,虽然函数内部并不需要这些参数;
  • 传入的参数少,也不会报错,函数参数将收到 undefined,计算结果为 NaN

为避免收到 undefined,可以对参数进行检查:

function abs(x) {
    if (typeof x !== 'number') {
        throw 'Not a number';
    }
    if (x >= 0) {
        return x;
    } else {
        return -x;
    }
}

(2)arguments

JavaScript 还有一个免费赠送的关键字 arguments,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments 类似 Array 但它不是一个 Array:

function foo(x) {
    console.log('x = ' + x); // 10
    for (var i=0; i<arguments.length; i++) {
        console.log('arg ' + i + ' = ' + arguments[i]); // 10, 20, 30
    }
}
foo(10, 20, 30);

/*
x = 10
arg 0 = 10
arg 1 = 20
arg 2 = 30
*/

arguments 最常用于判断传入参数的个数

(3)rest 参数

如果使用 arguments 获取除已定义参数之外的参数,比较麻烦。所以,ES6 标准引入了 rest 函数,用于获取多余的传参。

function fooo (a, b, ...rest) {
    console.log('a = ' + a);
    console.log('b = ' + b);
    console.log('rest = ' + rest);
}
// 多传参:rest会获取多余参数
fooo(1, 2, 3, 4, 5)
/*
a = 1
b = 2
rest = 3,4,5
 */

// 少传参:rest参数会接受一个空数组
fooo(1)
/*
a = 1
b = undefined
[]
*/

(4)return 语句坑

由于 javascript 在行末自动添加分号,所以 return 语句需要写在同一行,或{...}中

// 正常的单行return
function foooo() {
    return { name: 'foo' };
}
foooo(); // { name: 'foo' }

// 多行return的坑
function fooooo() {
    return
    { name: 'foo' };
}
fooooo(); // undefined

// 正确的多行return写法
function foo() {
    return { // 这里不会自动加分号,因为{表示语句尚未结束
        name: 'foo'
    };
}

(5)变量作用域与解构赋值

A. var申明的变量是有作用域的,作用域为整个函数体;
  • 由于 JavaScript 的函数可以嵌套,此时,内部函数可以访问外部函数定义的变量,反过来则不行
  • JavaScript 的函数在查找变量时从自身函数定义开始,从 “内” 向 “外” 查找。如果内部函数定义了与外部函数重名的变量,则内部函数的变量将 “屏蔽” 外部函数的变量。
B. 变量提升

JavaScript 的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量 “提升” 到函数顶部

function foo() {
    var x = 'Hello, ' + y;
    console.log(x);
    var y = 'Bob';
}
foo();  // Hello, undefined
/*
因为JavaScript引擎自动提升了变量y的声明,所以函数不会报错;
但是不会提升变量y的赋值,所以变量y的值为undefined
*/

常见写法:用一个 var 在函数开头 申明所有变量

function foo() {
    var
        x = 1, // x初始化为1
        y = x + 1, // y初始化为2
        z, i; // z和i为undefined
    // 其他语句:
    for (i=0; i<100; i++) {
        ...
    }
}
C. 全局作用域

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

var course = 'Learn JavaScript';
alert(course); // 'Learn JavaScript'
alert(window.course); // 'Learn JavaScript'

你可能猜到了,由于函数定义有两种方式,以变量方式 var foo = function () {}定义的函数实际上也是一个全局变量,因此,顶层函数的定义也被视为一个全局变量,并绑定到 window 对象;
我们每次直接调用的 alert() 函数其实也是 window 的一个变量:

function foo() {
    alert('foo');
}

foo(); // 直接调用foo()
window.foo(); // 通过window.foo()调用

说明,JavaScript 只有一个全局作用域。任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续网上查找,最后如果在全局作用域中也没找到,则报 ReferennceError 错误。

D. 名字空间

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

// 唯一的全局变量MYAPP:
var MYAPP = {};

// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;

// 其他函数:
MYAPP.foo = function () {
    return 'foo';
};

把自己的代码全部放入唯一的名字空间 MYAPP 中,会大大减少全局变量冲突的可能。jQuery、YUI、underscore 也这么做。

E. 局部作用域

var 定义的变量作用域:函数内部
let 定义的变量作用域:块

// var定义变量作用域在函数内
function foo() {
    for (var i=0; i<100; i++) {
        //
    }
    i += 100; // 仍然可以引用变量i
}

// let定义变量可以达到块级作用域
function foo() {
    var sum = 0;
    for (let i=0; i<100; i++) {
        sum += i;
    }
    // SyntaxError:
    i += 1;
}

F. 常量

(ES6)const 申明常量。const 和 let 都具有块级作用域。

const PI = 3.14;
PI = 3; // 某些浏览器不报错,但是无效果!
PI; // 3.14
G. 解构赋值

ES6 引入:解构赋值,可以同时对一组变量进行赋值。

// 传统写法
var array = ['hello', 'Javascript', 'ES6']
var x = array[0];
var y = array[1];
var z = array[2];

// 如果浏览器支持解构赋值就不会报错:
var [x, y, z] = ['hello', 'JavaScript', 'ES6'];

注意,对数组元素进行解构赋值时,多个变量要用 [...] 括起来。
如果数组本身还有嵌套,也可以通过下面的形式进行解构赋值,注意嵌套层次和位置要保持一致:

let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
x; // 'hello'
y; // 'JavaScript'
z; // 'ES6'

解构赋值还可以忽略某些元素:

let [, , z] = ['hello', 'JavaScript', 'ES6']; // 忽略前两个元素,只对z赋值第三个元素
z; // 'ES6'

使用解构赋值,从对象中取出若干属性,便于快速获取对象的指定属性。

var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school'
};
var {name, age, passport} = person;

对一个对象进行解构赋值时,同样可以直接对嵌套的对象属性进行赋值,只要保证对应的层次是一致的:

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

使用解构赋值对对象属性进行赋值时,如果对应的属性不存在,变量将被赋值为 undefined,这和引用一个不存在的属性获得 undefined 是一致的。如果要使用的变量名和属性名不一致,可以用下面的语法获取:

var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school'
};

// 把passport属性赋值给变量id:
let {name, passport:id} = person;
name; // '小明'
id; // 'G-12345678'
// 注意: passport不是变量,而是为了让变量id获得passport属性:
passport; // Uncaught ReferenceError: passport is not defined

解构赋值还可以使用默认值,这样就避免了不存在的属性返回 undefined 的问题:

var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678'
};

// 如果 person 对象没有 single 属性,默认赋值为 true:

var {name, single=true} = person;
name; // '小明'
single; // true

有些时候,如果变量已经被声明了,再次赋值的时候,正确的写法也会报语法错误:

// 声明变量:
var x, y;
// 解构赋值:
{x, y} = { name: '小明', x: 100, y: 200};
// 语法错误: Uncaught SyntaxError: Unexpected token =

这是因为 JavaScript 引擎把{开头的语句当作了块处理,于是=不再合法。解决方法是用小括号括起来:

({x, y} = { name: '小明', x: 100, y: 200});
H. 使用场景
  • 解构赋值可以简化代码 比如交换两个变量的值 javascript var x=1, y=2; [x, y] = [y, x]

4、方法

(1)函数内部的 this 到底指向谁?

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

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: getAge()
}
xiaoming.age();  // 30,正常结果
getAge();  // NaN

JavaScript 函数中如果调用了 this,那么 this 到底指向谁?

  • 如果以对象对方法形式调用,比如 xiaoming.age(),该函数的 this 指向被调用的函数,也就是 xiaoming,符合预期。
  • 如果单独调用函数,比如 getAge(),此时,该函数的 this 指向全局对象,也就是 window。 注意:如果以对象的方法形式调用,格式必须是: obj.xxx();

apply 与 call

指定函数的 this 指向哪个对象,可以使用 apply 和 call:

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

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: getAge()
}
xiaoming.age(); // 30
getAge().apply(xiaoming, [函数参数])
getAge().call(xiaoming, 函数参数)
// 对于普通函数调用,通常把this帮定为null
Math.max.apply(null, [1, 3, 5])
Math.max.call(null, 1, 3, 5)

装饰器

javaScript 的所有对象都是动态的,即使是内置的函数,也可以重新指向新的函数。
比如统计调用了多少次 parceInt(),可以如下:

'use strict';

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

parseInt = function () {  // 动态改变parseInt函数的行为
    count += 1;
    return oldParseInt.apply(null, arguments);  // 调用原函数
}

parseInt('10');
parseInt('20');
parseInt('30');
console.log(count)

高阶函数

接收函数作为参数的函数,称之为高阶函数。
一个最简单的高阶函数:

function add(x, y, f) {
    return f(x) + f(y);
}

(1)map

由于 map() 方法定义在 JavaScript 的 Array 中,我们调用 Array 的 map() 方法,传入我们自己的函数,就得到了一个新的 Array 作为结果:
map() 只接受一个入参的函数;

function pow(x) {
    return x * x;
}
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var results = arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81]
console.log(results);

注意:map() 传入的参数是 pow,即函数对象本身。
map() 作为高阶函数,实际上把运算规则抽象了。

(2)reduce

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

[x1, x2, x3, x4].reduce(f) =  f(f(f(x1, x2), x3), x4)

(3)filter

filter() 接收一个函数,根据返回值是 true 还是 false 决定保留还是丢弃该元素。filter 接收的回调函数可以接收多个参数;
比如,去除 Array 中的重复元素:

var r, arr = ['apple', 'strawberry', 'banana', 'pear', 'apple', 'orange', 'orange', 'strawberry'];
r = arr.filter(function ( element, index, self ) {
    return self.indexOf(element) === index;
})

(4)sort

惊掉大牙的 sort() 结果

// 无法理解的结果:
[10, 20, 1, 2].sort(); // [1, 10, 2, 20]

原因:因为 Array 的 sort() 方法默认把所有元素先转换为 String 再排序,结果'10'排在了'2'的前面,因为字符'1'比字符'2'的 ASCII 码小。

sort() 是高阶函数,可以接收一个比较函数来实现自定义的排序

var arr = [10, 20, 1, 2];
arr.sort(function (x, y) {
    let a = 0;
    if (x < y) {
        a = -1;
    }
    if (x > y) {
        a = 1;
    }
    return a;
});
console.log(arr); // [1, 2, 10, 20]

最后友情提示,sort() 方法会直接对 Array 进行修改,它返回的结果仍是当前 Array:

var a1 = ['B', 'A', 'C'];
var a2 = a1.sort();
a1; // ['A', 'B', 'C']
a2; // ['A', 'B', 'C']
a1 === a2; // true, a1和a2是同一对象

every

every() 方法可以判断数组的所有元素是否满足测试条件。
例如:判断一个包含若干字符串的数组,判断所有字符串睡佛欧满足指定的测试条件:

var arr = ['Apple', 'pear', 'orange']
console.log(arr.every(function (s) {
    return s.length > 0;
}))  // true 因为每个元素都满足s.length>0

console.log(arr.every(function (s) {
    return s.toLowerCase() === s;
}))  // false 因为不是每个元素都全部是小写

find

find() 方法用于查找符合条件的第一个元素,如果找到了,返回这个元素,否则,返回 undefined;

var arr = ['Apple', 'pear', 'orange']
console.log(arr.find(function (s) {
    return s.toLowerCase() === s;
}));  // 'pear', 因为pear全部是小写

console.log(arr.find(function (s) {
    return s.toUpperCase() === s;
}));  // undefined, 因为没有全部是大写的元素

findIndex

findIndex() 和 find() 类似,也是查找符合条件的第一个元素,不同之处在于 findIndex() 会返回这个元素的索引,如果没有找到,返回-1;

forEach

forEach() 和 map() 类似,它也把每个元素依次作用于传入的函数,但不会返回新的数组。forEach() 常用于遍历数组,因此,传入的函数不需要返回值

闭包

高阶函数除了可以接收函数作为参数外,还可以把函数作为结果值返回。

箭头函数

ES6 标准新增了一种新的函数:Arrow Function(箭头函数)
箭头函数相当于一个匿名函数:

x => x * x
//相当于
function (x) {
  return x * x;
}

generator

gennerator(生成器)是 ES6 标准引入的新的数据类型。一个 generator 看上去像一个函数,但可以往返多次。
generator 的函数,在每次调用 next() 的时候执行,遇到 yield 语句返回,再次执行时从上次返回的 yield 语句处继续执行。

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

调用 generator 对象有两种方法:

  • next():next() 方法会执行 generator 的代码,然后,每次遇到 yield,就返回一个对象{value: x, done: true/false},然后 “暂停”。返回的 value 就是 yield 的返回值,done 表示这个 generator 是否已经执行结束了。如果 done 为 true,则 value 就是 return 的返回值。 当执行到 done 为 true 时,这个 generator 对象就已经全部执行完毕你,不要再继续调用 next() 了。
var f = fib(5);
f.next(); // {value: 0, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 2, done: false}
f.next(); // {value: 3, done: false}
f.next(); // {value: undefined, done: true}
  • 直接用 for ... of 循环迭代 generator 对象,这种方式不需要我们自己判断 done。
var fib_obj = fib(10);
for (var x of fib_obj) {
    console.log(x);  // 依次输出0, 1, 1, 2, 3, ...
}

对象

标准对象

在 JavaScript 的世界里,一切都是对象。typeof 获取对象的类型。

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 = 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 操作符可以判断出 number、boolean、string、function 和 undefined;
  • 判断 Array 要使用 Array.isArray(arr);
  • 判断 null 请使用 myVar === null;
  • 判断某个全局变量是否存在用 typeof window.myVar === 'undefined';
  • 函数内部判断某个变量是否存在用 typeof myVar === 'undefined'。

注意:

  • null 和 undefined 没有 toSrting() 方法;
  • number 对象调用 toString() 报错 SyntaxError:
123.toString(); // SyntaxError
// number转string要特殊处理以下
123..toString(); // '123', 注意是两个点!
(123).toString(); // '123'

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形式表示的时间戳

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

// 如果要创建一个指定日期和时间的Date对象,可以用:
var d = new Date(2015, 5, 19, 20, 15, 30, 123);
d; // Fri Jun 19 2015 20:15:30 GMT+0800 (CST)

JavaScript 神坑:JavaScript 的月份范围用整数表示是 0~11,0 表示一月,1 表示二月……。此处应该是设计者的 BUG。

// 标准时间转换为时间戳
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小时

时间戳:时间戳是一个自增的整数,它表示从1970年1月1日零时整的 GMT 时区开始的那一刻,到现在的毫秒数。假设浏览器所在电脑的时间是准确的,那么世界上无论哪个时区的电脑,它们此刻产生的时间戳数字都是一样的,所以,时间戳可以精确地表示一个时刻,并且与时区无关。

RegExp 正则表达式

在正则表达式中,如果直接给出字符,就是精确匹配。匹配规则如下:

正则基础

  • 用\d 可以匹配一个数字;
  • \w 可以匹配一个字母或数字;
  • \s 可以匹配一个空格(也包括 Tab 等空白符),所以\s+ 表示至少有一个空格,例如匹配' ','\t\t'等;
  • 要匹配变长的字符,在正则表达式中,用 * 表示任意个字符(包括 0 个),用 + 表示至少一个字符,用?表示 0 个或 1 个字符,用{n}表示 n 个字符,用{n,m}表示 n-m 个字符:

进阶

要做更精确地匹配,可以用 [] 表示范围,比如:
(1)[0-9a-zA-Z_] 可以匹配一个数字、字母或者下划线;
(2)[0-9a-zA-Z_]+ 可以匹配至少由一个数字、字母或者下划线组成的字符串,比如'a100','0_Z','js2015'等等;
(3)[a-zA-Z_\$][0-9a-zA-Z_\$]* 可以匹配由字母或下划线、$ 开头,后接任意个由一个数字、字母或者下划线、$ 组成的字符串,也就是 JavaScript 允许的变量名;
(4)[a-zA-Z_\$][0-9a-zA-Z_\$]{0, 19}更精确地限制了变量的长度是 1-20 个字符(前面 1 个字符 + 后面最多 19 个字符)。
(5)A|B 可以匹配 A 或 B,所以 (J|j) ava(S|s) cript 可以匹配'JavaScript'、'Javascript'、'javaScript'或者'javascript'。
(6)表示行的开头,\d 表示必须以数字开头。
(7)$ 表示行的结束,\d$ 表示必须以数字结束。

你可能注意到了,js 也可以匹配'jsp',但是加上js$ 就变成了整行匹配,就只能匹配'js'了。

正则的使用

JavaScript 有两种方式创建正则表达式:

var re1 = /ABC\-001/;
var re2 = new RegExp('ABC\\-001');

re1; // /ABC\-001/
re2; // /ABC\-001/

// 判断正则是否匹配
var re = /^\d{3}\-\d{3,8}$/;
re.test('010-12345'); // true
re.test('010-1234x'); // false
re.test('010 12345'); // false

切分字符串

// 用正则表达式切分字符串比用固定的字符更灵活,请看正常的切分代码:
'a b   c'.split(' '); // ['a', 'b', '', '', 'c']
// 嗯,无法识别连续的空格,用正则表达式试试:
'a b   c'.split(/\s+/); // ['a', 'b', 'c']
// 无论多少个空格都可以正常分割。加入,试试:
'a,b, c  d'.split(/[\s\,]+/); // ['a', 'b', 'c', 'd']
// 再加入;试试:
'a,b;; c  d'.split(/[\s\,\;]+/); // ['a', 'b', 'c', 'd']
// 如果用户输入了一组标签,下次记得用正则表达式来把不规范的输入转化成正确的数组。

分组

()表示的就是要提取的分组(Group)。

// ^(\d{3})-(\d{3,8})$分别定义了两个组,可以直接从匹配的字符串中提取出区号和本地号码:
var re = /^(\d{3})-(\d{3,8})$/;
re.exec('010-12345'); // ['010-12345', '010', '12345']
re.exec('010 12345'); // null

如果正则表达式中定义了组,就可以在 RegExp 对象上用 exec() 方法提取出子串来。

  • exec() 方法在匹配成功后,会返回一个 Array,第一个元素是正则表达式匹配到的整个字符串,后面的字符串表示匹配成功的子串。
  • exec() 方法在匹配失败时返回 null。

贪婪匹配

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

// 需要特别指出的是,正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。举例如下,匹配出数字后面的0:
var re = /^(\d+)(0*)$/;
re.exec('102300'); // ['102300', '102300', '']
// 由于\d+采用贪婪匹配,直接把后面的0全部匹配了,结果0*只能匹配空字符串了。
// 必须让\d+采用非贪婪匹配(也就是尽可能少匹配),才能把后面的0匹配出来,加个?就可以让\d+采用非贪婪匹配:
var re = /^(\d+?)(0*)$/;
re.exec('102300'); // ['102300', '1023', '00']

全局搜索

JavaScript 的正则表达式还有几个特殊的标志,最常用的是 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,直到结束仍没有匹配到

全局匹配类似搜索,因此不能使用/...$/,那样只会最多匹配一次。

正则表达式还可以指定 i 标志,表示忽略大小写,m 标志,表示执行多行匹配。

JSON

发明者:道格拉斯·克罗克福特(Douglas Crockford)
诞生时间:2002 年
JSON 中数据结构:

  • number:和 JavaScript 的 number 完全一致;
  • boolean:就是 JavaScript 的 true 或 false;
  • string:就是 JavaScript 的 string;
  • null:就是 JavaScript 的 null;
  • array:就是 JavaScript 的 Array 表示方式——[];
  • object:就是 JavaScript 的{ ... }表示方式。 JSON 字符集:必须是 UTF-8 注意:为了统一解析,JSON 的字符串规定必须用双引号"",Object 的键也必须用双引号""。

JavaScript 中可以直接使用 JSON,因为 javascript 内置了 JSON。

序列化

将 JavaScript 对象序列化成 JSON 格式的字符串

var xiaoming = {
    name: '小明',
    age: 14,
    gender: true,
    height: 1.65,
    grade: null,
    'middle-school': '\"W3C\" Middle School',
    skills: ['JavaScript', 'Java', 'Python', 'Lisp']
};

var s = JSON.stringify(xiaoming);
console.log(s);

// 要输出的好看些,按照缩进输出:
var s = JSON.stringify(xiaoming, null, '  ');   
console.log(s);

// 第二个参数表示输出指定的属性
var s = JSON.stringify(xiaoming, ['name', 'height'], '  ');

// 还可以传入一个函数,这样对象的每个键值对都会被函数先处理
function convert(key, value) {
    if (typeof value === 'string') {
        return value.toUpperCase();
    }
    return value;
}

JSON.stringify(xiaoming, convert, '  ');

// 如果还想要精确控制如何序列化对象,可以给对象定义一个toJSON()的方法,直接返回JSON应该序列化的数据:
var xiaoming = {
    name: '小明',
    age: 14,
    gender: true,
    height: 1.65,
    grade: null,
    'middle-school': '\"W3C\" Middle School',
    skills: ['JavaScript', 'Java', 'Python', 'Lisp'],
    toJSON: function () {
        return { // 只输出name和age,并且改变了key:
            'Name': this.name,
            'Age': this.age
        };
    }
};

JSON.stringify(xiaoming); // '{"Name":"小明","Age":14}'

反序列化

用 JSON.parse() 将 JSON 格式的字符串转为 JavaScript 对象;

let js_obj = JSON.parse('{"Name":"小明","Age":14}')
console.log(js_obj);

JSON.parse() 还可以接收一个函数,用来转换解析出的属性:

let js_obj1 = JSON.parse('{"Name":"小明","Age":14}', function (key, value) {
    if (key === 'Name') {
        return value + '小伙伴';
    }
    return value;
})

console.log(JSON.stringify(js_obj1))

/* 输出:
{"Name":"小明小伙伴","Age":14}
*/

面向对象编程

JavaScript 的所有数据都可以看成对象。

JavaScript 的面向对象编程和大多数其他语言如 Java、C# 的面向对象编程都不太一样。
Java 的面向对象有两个基本概念:

  • 类:类是对象的类系 ing 模版,例如,定义 Student 类来表示学生,类本身是一种类型;
  • 实例:实例谁根据类创建的对象,例如 Student 类可以创建出 xiaoming、xiaohong 等多个实例;

JavaScript 不区分类和实例的概念,而是通过原型 (prototype) 来实现面向对象编程。

var Student = {
    name: 'Robot',
    height: 1.2,
    run: function () {
        console.log(this.name + ' is running...');
    }
};

function createStudent (name) {
    //基于Student原型创建一个新对象
    var s = Object.create(Student);
    // 初始化新对象
    s.name = name;
    return s;
}

var xiaoming = createStudent('小明');
xiaoming.run();  // 小明 is running...
xiaoming.__proto__ === Student;  // true

JavaScript 的原型链和 Java 的 Class 区别就在,JS 没有 “Class” 的概念,所有对象都是实例,所谓继承关系不过是吧一个对象的原型指向另一个对象而已。

创建对象

JavaScript 对每个创建的对象都会设置一个原型,指向它的原型对象。
当我们用 obj.xxx 访问一个对象的属性时,JavaScript 引擎现在当前对象上查找该属性,如果没有找到,就到其原型对象上找,如果还没有找到,就一直上溯到 Object.prototype 对象,最后,如果还没有找到,就只能返回 undefiined。

例如:创建一个 Array 对象:

var arr = [1, 2, 3];

其原型链是:

arr  ----->  Array.prototype  ----->  Object.prototype  -----> null

Array.prototype 定义了 indexOf()、shift() 等方法,因此所有的 Array 对象可以直接调用这些方法。
注意:如果原型链很长,那么访问一个对象的属性就会因为花更多的时间查找而变得更慢,因此要注意不要把原型链搞的太长。

构造函数

除了直接用{ ... }创建一个对象外,还可以用构造函数的方法来创建对象。

function Student(name) {
    this.name = name;
    this.hello = function () {
        alert('Hello, ' + this.name + '!');
    }
}

var xiaoming = new Student('小明');
xiaoming.name; // '小明'
xiaoming.hello(); // Hello, 小明!

注意:如果不写 new,这就是个普通函数,它返回 undefined。如果写了 new,它就变成了一个构造函数,它绑定的 this 指向新创建的对象,并默认返回 this。

忘记写 new 怎么办?

千万不要忘记写 new。在 strict 模式下,this.name = name 将报错,因为 this 绑定为 undefined,在非 strict 模式下,this.name = name 不报错,因为 this 绑定为 window,于是无意间创建了全局变量 name,并且返回 undefined,这个结果更糟糕。

所以,调用构造函数千万不要忘记写 new。为了区分普通函数和构造函数,按照约定,构造函数首字母应当大写,而普通函数首字母应当小写,这样,一些语法检查工具如 jslint 将可以帮你检测到漏写的 new。

最后,可以编写一个 createStudent() 函数,在内部封装所有的 new 操作:


function Student(props) {
    this.name = props.name || '匿名'; // 默认值为'匿名'
    this.grade = props.grade || 1; // 默认值为1
}

Student.prototype.hello = function () {
    alert('Hello, ' + this.name + '!');
};

function createStudent(props) {
    return new Student(props || {})
}

var xiaoming = createStudent({name: '小明'});
console.log(xiaoming.grade);

原型继承

JavaScript 的原型继承实现方式就是:

  • 定义新的构造函数,并在内部用 call() 调用希望 “继承” 的构造函数,并绑定 this;
  • 借助中间函数 F 实现原型链继承,最好通过封装的 inherits 函数完成;
  • 继续在新的构造函数的原型上定义新方法。
function Student(props) {
    this.name = props.name || 'Unnamed';
}

Student.prototype.hello = function () {
    alert('Hello, ' + this.name + '!');
}

// PrimaryStudent构造函数
function PrimaryStudent (props) {
    // 调用Student构造函数,绑定this变量
    Student.call(this, props);
    this.grade = props.grade || 1;
}

// 原型继承函数
function inherits (Child, Parent) {
    // 空函数F
    var F = function () {};
    // 把F的原型指向Parent.prototype:
    F.prototype = Parent.prototype;
    // 把Child的原型指向一个新的F对象,F对象的原型正好指向Parent.prototype:
    Child.prototype = new F();
    // 把Child原型的构造函数修复为Child:
    Child.prototype.constructor = Child;
}

// 实现原型继承链
inherits(PrimaryStudent, Student);

class 继承

原型继承理解困难,实现需要编写大量代码,有没有更简单的办法?有
新的关键字 class 从 ES6 开始正式被引入到 JavaScript 中。class 的目的就是让定义类更简单。

(1)class 定义原型

class Student {
    constructor (name) {
        this.name = name;
    }

    hello() {
        alert('Hello ' + this.name + '!');
    }
}

var xiaoming = new Student('小明');
xiaoming.hello();

(2)class 继承

// extends则表示原型链对象来自Student
class PrimaryStudent extends Student{
    // 构造函数
    constructor (name, grade) {
        //通过super(name)来调用父类的构造函数
        super(name);
        this.grade = grade;
    }

    myGrade() {
        return this.grade;
    }
}

浏览器

浏览器对象

(1)window

window 对象不但充当全局作用域,而且表示浏览器窗口。
内部宽高:指除去菜单栏、工具栏、边框等占位元素后,用于显示网页的净宽高。

  • window.innerWidth:浏览器窗口的内部宽度;
  • window.innerHeight:浏览器窗口的内部高度;

(2)navigator

navigator 对象表示浏览器的信息,最常用的属性包括:

  • navigator.appName:浏览器名称;
  • navigator.appVersion:浏览器版本;
  • navigator.language:浏览器设置的语言;
  • navigator.platform:操作系统类型;
  • navigator.userAgent:浏览器设定的 User-Agent 字符串。

请注意,navigator 的信息可以很容易地被用户修改,所以 JavaScript 读取的值不一定是正确的

(3)screen

screen 对象表示屏幕的信息,常用的属性有:

  • screen.width:屏幕宽度,以像素为单位;
  • screen.height:屏幕高度,以像素为单位;
  • screen.colorDepth:返回颜色位数,如 8、16、24。

(4)location

location 对象表示当前页面的 URL 信息。

  • location.href:获取当前页面的 URL 信息
  • location.protocol; // 'http'
  • location.host; // 'www.example.com'
  • location.port; // '8080'
  • location.pathname; // '/path/index.html'
  • location.search; // '?a=1&b=2'
  • location.hash; // 'TOP'

  • location.assign():加载新页面

(5)document

document 对象表示当前页面。由于 HTML 在浏览器中以 DOM 形式表示为树形结构,document 对象就是整个 DOM 树的根节点。

document 的 title 属性是从 HTML 文档中的

xxx读取的,但是可以动态改变:
document.title = '努力学习JavaScript!';

要查找 DOM 树的某个节点,需要从 document 对象开始查找。最常用的查找是根据 ID 和 Tag Name。

  • 获取页面对象:用 document 对象提供的 getElementById() 和 getElementsByTagName() 可以按 ID 获得一个 DOM 节点和按 Tag 名称获得一组 DOM 节点
  • 获取页面 cookie:JavaScript 可以通过 document.cookie 读取到当前页面的 Cookie: javascript document.cookie; // 'v=123; remember=true; prefer=zh' 由于 JavaScript 可以读取页面的 Cookie,而用户的登录信息通常也保存在 Cookie 中,这就造成了巨大的安全隐患。为了解决这个问题,服务器在设置 Cookie 时,可以使用 httponly,设定了 httponly 的 Cookie 将不能被 JavaScript 读取。这个行为由浏览器实现,主流浏览器均支持 httponly 选项。 设置后利用开发者工具查看,会显示 HttpOnly 的✅

(6)history

任何时候,都不应该使用 history 这个对象了。

操作 DOM

由于 HTML 文档被浏览器解析后就是一棵 DOM 树,要改变 HTML 的结构,就需要通过 JavaScript 来操作 DOM。
始终记住 DOM 是一个树形结构。操作一个 DOM 节点实际上就是这么几个操作:

  • 更新:更新该 DOM 节点的内容,相当于更新了该 DOM 节点表示的 HTML 的内容;
  • 遍历:遍历该 DOM 节点下的子节点,以便进行进一步操作;
  • 添加:在该 DOM 节点下新增一个子节点,相当于动态增加了一个 HTML 节点;
  • 删除:将该节点从 HTML 中删除,相当于删掉了该 DOM 节点的内容以及它包含的所有子节点。 ```javascript // 返回 ID 为'test'的节点: var test = document.getElementById('test');

// 先定位 ID 为'test-table'的节点,再返回其内部所有 tr 节点:
var trs = document.getElementById('test-table').getElementsByTagName('tr');

// 先定位 ID 为'test-div'的节点,再返回其内部所有 class 包含 red 的节点:
var reds = document.getElementById('test-div').getElementsByClassName('red');

// 获取节点 test 下的所有直属子节点:
var cs = test.children;

// 获取节点 test 下第一个、最后一个子节点:
var first = test.firstElementChild;
var last = test.lastElementChild;

// 通过 querySelector 获取 ID 为 q1 的节点:
var q1 = document.querySelector('#q1');

// 通过 querySelectorAll 获取 q1 节点内的符合条件的所有节点:
var ps = q1.querySelectorAll('div.highlighted > p');


#### 更新DOM
更新方式由两种:
(1)修改innerHTML属性:这个方式非常强大,不但可以修改一个DOM节点的文本内容,还可以直接通过HTML片段修改DOM节点内部的子树:
```javascript
// 获取<p id="p-id">...</p>
var p = document.getElementById('p-id');
// 设置文本为abc:
p.innerHTML = 'ABC'; // <p id="p-id">ABC</p>
// 设置HTML:
p.innerHTML = 'ABC <span style="color:red">RED</span> XYZ';
// <p>...</p>的内部结构已修改

(2)修改 innerText 或 textContent 属性,这样可以自动对字符串进行 HTML 编码,保证无法设置任何 HTML 标签:

// 获取<p id="p-id">...</p>
var p = document.getElementById('p-id');
// 设置文本:
p.innerText = '<script>alert("Hi")</script>';
// HTML被自动编码,无法设置一个<script>节点:

(3)修改 css 属性

// 获取<p id="p-id">...</p>
var p = document.getElementById('p-id');
// 设置CSS:
p.style.color = '#ff0000';
p.style.fontSize = '20px';
p.style.paddingTop = '2em';

插入 DOM

(1)如果这个 DOM 节点是空的,例如,

,那么,直接使用 innerHTML = 'child'就可以修改 DOM 节点的内容,相当于 “插入” 了新的 DOM 节点。

(2)如果这个 DOM 节点不是空的,那就不能这么做,因为 innerHTML 会直接替换掉原来的所有子节点。
有两个办法可以插入新的节点:

  • 一个是使用 appendChild,把一个子节点添加到父节点的最后一个子节点。例如:

    <!-- HTML结构 -->
    <p id="js">JavaScript</p>
    <div id="list">
    <p id="java">Java</p>
    <p id="python">Python</p>
    <p id="scheme">Scheme</p>
    </div>
    

    JavaScript

    添加到 的最后一项:
    var
    js = document.getElementById('js'),
    list = document.getElementById('list');
    list.appendChild(js);
    
  • 有时会从零创建一个新的节点,然后插入到指定的位置

    var
    list = document.getElementById('list'),
    haskell = document.createElement('p');
    haskell.id = 'haskell';
    haskell.innerText = 'Haskell';
    list.appendChild(haskell);
    
  • (3)insertBefore
    使用 parentElement.insertBefore(newElement, referenceElement);,子节点会插入到 referenceElement 之前。

    var
        list = document.getElementById('list'),
        ref = document.getElementById('python'),
        haskell = document.createElement('p');
    haskell.id = 'haskell';
    haskell.innerText = 'Haskell';
    list.insertBefore(haskell, ref);
    

    (4)删除 DOM

    要删除一个节点,首先要获得该节点本身以及它的父节点,然后,调用父节点的 removeChild 把自己删掉:

    // 拿到待删除节点:
    var self = document.getElementById('to-be-removed');
    // 拿到父节点:
    var parent = self.parentElement;
    // 删除:
    var removed = parent.removeChild(self);
    removed === self; // true
    

    删除多个节点时,注意 children 属性时刻都在变化:

    var parent = document.getElementById('parent');
    parent.removeChild(parent.children[0]);
    parent.removeChild(parent.children[1]); // <-- 浏览器报错
    

    浏览器报错:parent.children[1] 不是一个有效的节点。原因就在于,当

    First

    节点被删除后,parent.children 的节点数量已经从 2 变为了 1,索引 [1] 已经不存在了。

    操作表单

    用 JavaScript 操作表单和操作 DOM 是类似的,因为表单本身也是 DOM 树。

    不过表单的输入框、下拉框等可以接收用户输入,所以用 JavaScript 来操作表单,可以获得用户输入的内容,或者对一个输入框设置新的内容。

    HTML 表单的输入控件主要有以下几种:

    文本框,对应的,用于输入文本;

    口令框,对应的,用于输入口令;

    单选框,对应的,用于选择一项;

    复选框,对应的,用于选择多项;

    下拉框,对应的,用于选择一项;

    隐藏文本,对应的,用户不可见,但表单提交时会把隐藏文本发送到服务器。

    (1)获取值
    如果我们获得了一个节点的引用,就可以直接调用 value 获得对应的用户输入值:

    // <input type="text" id="email">
    var input = document.getElementById('email');
    input.value; // '用户输入的值'
    

    这种方式可以应用于 text、password、hidden 以及 select。
    但是,对于单选框和复选框,value 属性返回的永远是 HTML 预设的值,而我们需要获得的实际是用户是否 “勾上了” 选项,所以应该用 checked 判断:

    // <label><input type="radio" name="weekday" id="monday" value="1"> Monday</label>
    var mon = document.getElementById('monday');
    mon.value; // '1'
    mon.checked; // true或者false
    

    (2)设置值
    设置值和获取值类似,对于 text、password、hidden 以及 select,直接设置 value 就可以:

    // <input type="text" id="email">
    var input = document.getElementById('email');
    input.value = 'test@example.com'; // 文本框的内容已更新
    

    对于单选框和复选框,设置 checked 为 true 或 false 即可。

    (3)HTML5 控件
    HTML5 新增了大量标准控件,常用的包括 date、datetime、datetime-local、color 等,它们都使用标签:

    <input type="date" value="2015-07-01">
    

    <input type="datetime-local" value="2015-07-01T02:03:04">
    

    <input type="color" value="#ff0000">
    

    不支持 HTML5 的浏览器无法识别新的控件,会把它们当做 type="text"来显示。支持 HTML5 的浏览器将获得格式化的字符串。例如,type="date"类型的 input 的 value 将保证是一个有效的 YYYY-MM-DD 格式的日期,或者空字符串。

    (4)提交表单
    JavaScript 以两种方式来处理表单的提交:
    A. 第一种方式:通过

    元素的 submit() 方法提交一个表单。
    例如,响应一个的 click 事件,在 JavaScript 代码中提交表单:
    <!-- HTML -->
    <form id="test-form">
        <input type="text" name="test">
        <button type="button" onclick="doSubmitForm()">Submit</button>
    </form>
    
    <script>
    function doSubmitForm() {
        var form = document.getElementById('test-form');
        // 可以在此修改form的input...
        // 提交form:
        form.submit();
    }
    </script>
    

    这种方式的缺点是扰乱了浏览器对 form 的正常提交。

    B. 第二种方式:响应

    本身的 onsubmit 事件,在提交 form 时作修改:
    <!-- HTML -->
    <form id="test-form" onsubmit="return checkForm()">
        <input type="text" name="test">
        <button type="submit">Submit</button>
    </form>
    
    <script>
    function checkForm() {
        var form = document.getElementById('test-form');
        // 可以在此修改form的input...
        // 继续下一步:
        return true;
    }
    </script>
    

    注意要 return true 来告诉浏览器继续提交,如果 return false,浏览器将不会继续提交 form,这种情况通常对应用户输入有误,提示用户错误信息后终止提交 form。
    在检查和修改时,要充分利用来传递数据。

    注意:没有 name 属性的的数据不会被提交。

    操作文件

    在 HTML 表单中,可以上传文件的唯一控件就是。
    注意:当一个表单包含<input type="file">时,表单的 enctype 必须指定为 multipart/form-data,method 必须指定为 post,浏览器才能正确编码并以 multipart/form-data 格式发送表单的数据。

    通常,上传的文件都由后台服务器处理,JavaScript 可以在提交表单时对文件扩展名做检查,以便防止用户上传无效格式的文件:

    var f = document.getElementById('test-file-upload');
    var filename = f.value; // 'C:\fakepath\test.png'
    if (!filename || !(filename.endsWith('.jpg') || filename.endsWith('.png') || filename.endsWith('.gif'))) {
        alert('Can only upload image file.');
        return false;
    }
    

    File API

    HTML5 新增的 File API 允许 JavaScript 读取文件内容,获取更多的文件信息。
    HTML5 的 File API 提供了FileFileReader两个主要对象,可以获得文件信息并读取文件。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>demo</title>
        <script src="test.js"></script>
    
    </head>
    <body>
    <form method="post" action="http://localhost/test" enctype="multipart/form-data">
        <p>图片预览</p>
        <div id="test-image-preview"
             style="border: 1px solid #ccc; width: 100%; height: 200px; background-size: contain; background-repeat: no-repeat; background-position: center center;"></div>
        <p>
            <input type="file" id="test-image-file" name="test" onclick="f()">
        </p>
        <p id="test-file-info"></p>
    </form>
    
    </body>
    </html>
    
    function f () {
        var
            fileInput = document.getElementById('test-image-file'),
            info = document.getElementById('test-file-info'),
            preview = document.getElementById('test-image-preview');
    // 监听change事件:
        fileInput.addEventListener('change', function () {
            // 清除背景图片:
            preview.style.backgroundImage = '';
            // 检查文件是否选择:
            if (!fileInput.value) {
                info.innerHTML = '没有选择文件...';
                return;
            }
            // 获取File引用:
            var file = fileInput.files[0];
            console.log(file);
            // 获取File信息:
            info.innerHTML = '文件: ' + file.name + '<br>' +
                '大小: ' + file.size + '<br>' +
                '修改: ' + file.lastModifiedDate;
            if (file.type !== 'image/jpeg' && file.type !== 'image/png' && file.type !== 'image/gif') {
                alert('不是有效的图片文件!');
                return false;
            }
            // 读取文件:
            var reader = new FileReader();
            reader.onload = function(e) {
                var
                    data = e.target.result; // 'data:image/jpeg;base64,/9j/4AAQSk...(base64编码)...'
                preview.style.backgroundImage = 'url(' + data + ')';
            };
            // 以DataURL的形式读取文件:
            reader.readAsDataURL(file);
        });
    }
    

    上面的代码演示了如何通过 HTML5 的 File API 读取文件内容。以 DataURL 的形式读取到的文件是一个字符串,类似于 data:image/jpeg;base64,/9j/4AAQSk...(base64 编码)...,常用于设置图像。如果需要服务器端处理,把字符串 base64,后面的字符发送给服务器并用 Base64 解码就可以得到原始文件的二进制内容。

    回调

    在 JavaScript 中,浏览器的 JavaScript 执行引擎在执行 JavaScript 代码时,总是以单线程模式执行,也就是说,任何时候,JavaScript 代码都不可能同时有多于 1 个线程在执行。
    在 JS 中,执行多任务实际上都是异步调用,比如上面的代码:

    reader.readAsDataURL(file);
    

    就会发起一个异步操作来读取文件内容。因为是异步操作,所以我们在 JavaScript 代码中就不知道什么时候操作结束,因此需要先设置一个回调函数:

    reader.onload = function(e) {
        // 当文件读取完成后,自动调用此函数:
    };
    

    当文件读取完成后,JavaScript 引擎将自动调用我们设置的回调函数。执行回调函数时,文件已经读取完毕,所以我们可以在回调函数内部安全地获得文件内容。

    AJAX(Asynchronous JavaScript and XML,即用 JS 执行异步网络请求)

    function success(text) {
        var textarea = document.getElementById('test-response-text');
        textarea.value = text;
    }
    
    function fail(code) {
        var textarea = document.getElementById('test-response-text');
        textarea.value = 'Error code: ' + code;
    }
    
    var request = new XMLHttpRequest(); // 新建XMLHttpRequest对象
    
    request.onreadystatechange = function () { // 状态发生变化时,函数被回调
        if (request.readyState === 4) { // 成功完成
            // 判断响应结果:
            if (request.status === 200) {
                // 成功,通过responseText拿到响应的文本:
                return success(request.responseText);
            } else {
                // 失败,根据响应码判断失败原因:
                return fail(request.status);
            }
        } else {
            // HTTP请求还在继续...
        }
    }
    
    // 发送请求:
    request.open('GET', 'https://testerhome.com/');
    request.send();
    
    alert('请求已发送,请等待响应...');
    

    当创建了 XMLHttpRequest 对象后,要先设置 onreadystatechange 的回调函数。在回调函数中,通常我们只需通过 readyState === 4 判断请求是否完成,如果已完成,再根据 status === 200 判断是否是一个成功的响应。

    XMLHttpRequest 对象的 open() 方法有 3 个参数,第一个参数指定是 GET 还是 POST,第二个参数指定 URL 地址,第三个参数指定是否使用异步,默认是 true,所以不用写。

    注意,千万不要把第三个参数指定为 false,否则浏览器将停止响应,直到 AJAX 请求完成。如果这个请求耗时 10 秒,那么 10 秒内你会发现浏览器处于 “假死” 状态。

    最后调用 send() 方法才真正发送请求。GET 请求不需要参数,POST 请求需要把 body 部分以字符串或者 FormData 对象传进去。

    安全限制

    浏览器的同源策略:默认情况下,JavaScript 在发送 AJAX 请求时,URL 的域名必须与当前页面完全一致。
    完全一致的意思是:

    • 域名要相同;
    • 协议要相同;
    • 端口号要相同;(有的浏览器口子松一点,允许端口不同,大多数浏览器都会严格遵守这个限制)

    解决 JavaScript 无法请求外域的 URL 问题

    • 【Flash】通过 Flash 插件发送 HTTP 请求,这种方式可以绕过浏览器的安全限制,但必须按照 Flash,并且跟 Flash 交互。不过 Flash 基本被抛弃了。
    • 【代理服务器】通过在同源域名下架设一个代理服务器来转发,JavaScript 负责把请求发送到代理服务器,代理服务器再把结果返回,这样就遵守了同源策略。这种方式麻烦之处在于需要服务器端额外做开发。 javascript '/proxy?url=http://www.sina.com.cn'
    • 【JSONP】JSONP 有个限制,只能用 GET 请求,并且要求返回 JavaScript。这种方式跨域实际上是利用了浏览器允许跨域引用 JavaScript 资源:
    • 【CORS】如果浏览器支持 HTML5,一劳永逸地使用新的跨域策略:CORS(Cross-Origin Resource Sharing) Origin 表示本域,也就是浏览器当前页面的域。当 JavaScript 向外域(如 sina.com)发起请求后,浏览器收到响应后,首先检查 Access-Control-Allow-Origin 是否包含本域,如果是,则此次跨域请求成功,如果不是,则请求失败,JavaScript 将无法获取到响应的任何数据。 假设本域是 my.com,外域是 sina.com,只要响应头 Access-Control-Allow-Origin 为http://my.com,或者是 *,本次请求就可以成功。 上面这种跨域请求,称之为 “简单请求”。简单请求包括 GET、HEAD 和 POST(POST 的 Content-Type 类型,仅限 application/x-www-form-urlencoded、multipart/form-data 和 text/plain),并且不能出现任何自定义头(例如 X-Custom: 12345),通常能满足 90% 的需求。

    Promise

    异步执行网络操作

    var ajax = ajaxGet('http://...');
    ajax.ifSuccess(success)
        .ifFail(fail);
    

    这种 “承诺将来会执行” 的对象在 JavaScript 中称为 Promise 对象。

    Promise 示例:

    new Promise(function (resolve, reject) {
        console.log('start new Promise...');
        var timeOut = Math.random() * 2;
        console.log('set timeout to: ' + timeOut + ' seconds.');
        setTimeout(function () {  // 承诺执行的代码
            if (timeOut < 1) {
                console.log('call resolve()...');
                resolve('200 OK');
            }
            else {
                console.log('call reject()...');
                reject('timeout in ' + timeOut + ' seconds.');
            }
        }, timeOut * 1000);
    }).then(function (r) {  // 处理结果代码
        console.log('Done: ' + r);
    }).catch(function (reason) {  // 处理结果代码
        console.log('Failed: ' + reason);
    });
    

    异步 - 串行 - 执行多个任务:

    // 其中,job1、job2和job3都是Promise对象
    job1.then(job2).then(job3).catch(handleError);
    

    实例:

    // 0.5秒后返回input*input的计算结果:
    function multiply(input) {
        return new Promise(function (resolve, reject) {
            console.log('calculating ' + input + ' x ' + input + '...');
            setTimeout(resolve, 500, input * input);
        });
    }
    
    // 0.5秒后返回input+input的计算结果:
    function add(input) {
        return new Promise(function (resolve, reject) {
            console.log('calculating ' + input + ' + ' + input + '...');
            setTimeout(resolve, 500, input + input);
        });
    }
    
    var p = new Promise(function (resolve, reject) {
        console.log('start new Promise...');
        resolve(123);
    });
    
    p.then(multiply)
        .then(add)
        .then(multiply)
        .then(add)
        .then(function (result) {
            console.log('Got value: ' + result);
        });
    
    

    并行 - 执行 - 多个任务

    并行执行多个任务,并返回结果:

    var p1 = new Promise(function (resolve, reject) {
        setTimeout(resolve, 500, 'P1');
    });
    var p2 = new Promise(function (resolve, reject) {
        setTimeout(resolve, 600, 'P2');
    });
    // 同时执行p1和p2,并在它们都完成后执行then:
    Promise.all([p1, p2]).then(function (results) {
        console.log(results); // 获得一个Array: ['P1', 'P2']
    });
    

    为了容错,执行多个同样功能的异步任务,只需要获得先返回的结果即可:

    var p1 = new Promise(function (resolve, reject) {
        setTimeout(resolve, 500, 'P1');
    });
    var p2 = new Promise(function (resolve, reject) {
        setTimeout(resolve, 600, 'P2');
    });
    Promise.race([p1, p2]).then(function (result) {
        console.log(result); // 'P1'
    });
    

    由于 p1 执行较快,Promise 的 then() 将获得结果'P1'。p2 仍在继续执行,但执行结果将被丢弃。

    Canvas(绘制图表和动画的幕布)

    jQuery

    jQuery 是一个 JavaScript 函数库。

    jQuery 是一个轻量级的"写的少,做的多"的 JavaScript 库。

    jQuery 库包含以下功能:

    HTML 元素选取
    HTML 元素操作
    CSS 操作
    HTML 事件函数
    JavaScript 特效和动画
    HTML DOM 遍历和修改
    AJAX
    Utilities

    错误处理

    最后请注意,catch 和 finally 可以不必都出现。也就是说,try 语句一共有三种形式:

    完整的 try ... catch ... finally:

    try {
        ...
    } catch (e) {
        ...
    } finally {
        ...
    }
    

    只有 try ... catch,没有 finally:

    try {
        ...
    } catch (e) {
        ...
    }
    

    只有 try ... finally,没有 catch:

    try {
        ...
    } finally {
        ...
    }
    

    错误类型

    JavaScript 有一个标准的 Error 对象表示错误,还有从 Error 派生的 TypeError、ReferenceError 等错误对象。我们在处理错误时,可以通过 catch(e) 捕获的变量 e 访问错误对象:

    抛出错误

    throw 抛出错误:throw new Error('错误名');

    错误传递

    如果在一个函数内部发生了错误,它自身没有捕获,错误就会被抛到外层调用函数,如果外层函数也没有捕获,该错误会一直沿着函数调用链向上抛出,直到被 JavaScript 引擎捕获,代码终止执行。

    所以,我们不必在每一个函数内部捕获错误,只需要在合适的地方来个统一捕获,一网打尽:

    异步错误处理

    牢记:JS 引擎是一个事件驱动的执行引擎,代码总是以单线程执行,而回调函数的执行需要等到下一个满足条件的时间出现后,才会被执行。

    JS 的异步代码,无法在调用时捕获,原因是:在捕获的当时,回调函数并未执行。
    例如:调用时捕获异步代码的异常,是捕获不到的

    function printTime() {
        throw new Error();
    }
    
    try {
        setTimeout(printTime, 1000);
        console.log('done');
    } catch (e) {
        console.log('error');
    }
    
    

    underscore

    成熟可靠的第三方开源库,使用统一的函数来实现 map()、filter() 这些操作;
    jQuery 在加载时,会把自身绑定到唯一的全局变量 $ 上,underscore 与其类似,会把自身绑定到唯一的全局变量_上,这也是为啥它的名字叫 underscore 的原因。

    Collections

    underscore 为集合类对象提供了一致的接口。集合类指 Array、Object,暂不支持 Map 和 Set。

    map/filter

    和 Array 的 map() 与 filter() 类似,但是 underscore 的 map() 和 filter() 可以作用于 Object。当作用于 Object 时,传入的函数为 function (value, key),第一个参数接收 value,第二个参数接收 key:

    var upper = _.map(obj, function (value, key) {
        return key + "=" + value.toUpperCase();
    });
    

    every/some

    当集合的所有元素都满足条件时,.every() 函数返回 true,当集合的至少一个元素满足条件时,.some() 函数返回 true:

    'use strict';
    // 所有元素都大于0?
    _.every([1, 4, 7, -3, -9], (x) => x > 0); // false
    // 至少一个元素大于0?
    _.some([1, 4, 7, -3, -9], (x) => x > 0); // true
    

    max / min

    这两个函数直接返回集合中最大和最小的数:

    groupBy

    shuffle / sample

    shuffle() 用洗牌算法随机打乱一个集合:

    'use strict';
    // 注意每次结果都不一样:
    _.shuffle([1, 2, 3, 4, 5, 6]); // [3, 5, 4, 6, 2, 1]
    

    sample() 则是随机选择一个或多个元素:

    'use strict';
    // 注意每次结果都不一样:
    // 随机选1个:
    _.sample([1, 2, 3, 4, 5, 6]); // 2
    // 随机选3个:
    _.sample([1, 2, 3, 4, 5, 6], 3); // [6, 1, 4]
    

    Arrarys

    first / last

    这两个函数分别取第一个和最后一个元素

    flatten

    flatten() 接收一个 Array,无论这个 Array 里面嵌套了多少个 Array,flatten() 最后都把它们变成一个一维数组

    zip / unzip

    zip() 把两个或多个数组的所有元素按索引对齐,然后按索引合并成新数组。例如,你有一个 Array 保存了名字,另一个 Array 保存了分数,现在,要把名字和分数给对上,用 zip() 轻松实现:

    var names = ['Adam', 'Lisa', 'Bart'];
    var scores = [85, 92, 59];
    _.zip(names, scores);
    // [['Adam', 85], ['Lisa', 92], ['Bart', 59]]
    

    unzip() 则是反过来:

    var namesAndScores = [['Adam', 85], ['Lisa', 92], ['Bart', 59]];
    _.unzip(namesAndScores);
    // [['Adam', 'Lisa', 'Bart'], [85, 92, 59]]
    

    object

    有时候你会想,与其用 zip(),为啥不把名字和分数直接对应成 Object 呢?别急,object() 函数就是干这个的:

    'use strict';
    var names = ['Adam', 'Lisa', 'Bart'];
    var scores = [85, 92, 59];
    _.object(names, scores);
    // {Adam: 85, Lisa: 92, Bart: 59}
    

    注意_.object() 是一个函数,不是 JavaScript 的 Object 对象。

    range

    uniq

    使用_.uniq 对数组元素进行不区分大小写去重:

    var arr = ['Apple', 'orange', 'banana', 'ORANGE', 'apple', 'PEAR'];
    var result = _.uniq(arr, x=>x.toUpperCase());
    // ["Apple", "orange", "banana", "PEAR"]
    

    Function

    underscore 提供了大量 JS 本身没有的高阶函数;

    bind

    bind() 可以帮我们把 s 对象直接绑定在 fn() 的 this 指针上,以后调用 fn() 就可以直接正常调用了:

    // 普通代码
    var s = ' Hello  ';
    var fn = s.trim;
    // 调用call并传入s对象作为this:
    fn.call(s)
    // 输出Hello
    
    // 使用_.bind
    var s = ' Hello  ';
    var fn = _.bind(s.trim, s);
    fn();
    // 输出Hello
    

    partial

    partial() 可以创建偏函数:即创建一个固定原函数的某一个参数的新函数.

    memoize

    如果一个函数调用开销很大,我们就可能希望能把结果缓存下来,以便后续调用时直接获得结果。举个例子,计算阶乘就比较耗时:

    once

    delay

    delay() 可以让一个函数延迟执行,效果和 setTimeout() 是一样的,但是代码明显简单了:

    object

    underscore 也提供了大量针对 Object 的函数

    keys / allkeys

    keys() 可以非常方便地返回一个 object 自身所有的 key,但不包含从原型链继承下来的:
    allKeys() 除了 object 自身的 key,还包含从原型链继承下来的:

    function Student(name, age) {
        this.name = name;
        this.age = age;
    }
    Student.prototype.school = 'No.1 Middle School';
    var xiaoming = new Student('小明', 20);
    _.keys(xiaoming); // ['name', 'age']
    _.allKeys(xiaoming); // ['name', 'age', 'school']
    

    values

    和 keys() 类似,values() 返回 object 自身但不包含原型链继承的所有值

    var obj = {
        name: '小明',
        age: 20
    };
    
    _.values(obj); // ['小明', 20]
    

    注意,没有 allValues(),原因我也不知道。

    mapObject

    mapObject() 就是针对 object 的 map 版本:

    var obj = { a: 1, b: 2, c: 3 };
    // 注意传入的函数签名,value在前,key在后:
    _.mapObject(obj, (v, k) => 100 + v); // { a: 101, b: 102, c: 103 }
    

    invert

    invert() 把 object 的每个 key-value 来个交换,key 变成 value,value 变成 key:

    var obj = {
        Adam: 90,
        Lisa: 85,
        Bart: 59
    };
    _.invert(obj); // { '59': 'Bart', '85': 'Lisa', '90': 'Adam' }
    

    extend / extendOwn

    extend() 把多个 object 的 key-value 合并到第一个 object 并返回:

    var a = {name: 'Bob', age: 20};
    _.extend(a, {age: 15}, {age: 88, city: 'Beijing'}); // {name: 'Bob', age: 88, city: 'Beijing'}
    // 变量a的内容也改变了:
    a; // {name: 'Bob', age: 88, city: 'Beijing'}
    

    注意:如果有相同的 key,后面的 object 的 value 将覆盖前面的 object 的 value。
    extendOwn() 和 extend() 类似,但获取属性时忽略从原型链继承下来的属性。

    clone

    如果我们要复制一个 object 对象,就可以用 clone() 方法,它会把原有对象的所有属性都复制到新的对象中

    var source = {
        name: '小明',
        age: 20,
        skills: ['JavaScript', 'CSS', 'HTML']
    };
    var copied = _.clone(source);
    // copied对象为:
    {
      "name": "小明",
      "age": 20,
      "skills": [
        "JavaScript",
        "CSS",
        "HTML"
      ]
    }
    

    注意,clone() 是 “浅复制”。所谓 “浅复制” 就是说,两个对象相同的 key 所引用的 value 其实是同一对象:

    source.skills === copied.skills; // true
    也就是说,修改 source.skills 会影响 copied.skills。

    isEqual

    isEqual() 对两个 object 进行深度比较,如果内容完全相同,则返回 true:

    Chaining

    chain() 可以把对象包装成能进程链式调用的方法:

    var r = _.chain([1, 4, 9, 16, 25])
             .map(Math.sqrt)
             .filter(x => x % 2 === 1)
             .value();
    

    Node.js

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 2 条回复 时间 点赞

自从舍弃 JQuery 投奔 VUE,写 js 的机会少了很多

simple 回复

最近要写 RN 测试底层服务,又开始看这块了

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册