JS遗珠

对于Javascript 的学习从未停止过,奈何其水深无法想象。JS基础回顾,作为前端部分开篇内容再好不过了。 其中包括了 聊雪峰、阮一峰、方应航等大神博文,还有j《avascript 高级程序设计》 一书的内容。

夯实基础,开卷有益。

basic

script 标签又六个属性。不要用延迟脚本。

阻断式语言:解释器求值完毕之前,网页不会加载。
解释器预处理,再执行。

手机端:js内部文件可以起到性能优化。

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

1
isNaN(NaN); // true

String 字符串

js 中 string 属于基本数据类型 (不同于 java 中的 String)

ES6新增了一种模板字符串,表示方法和上面的多行字符串一样,但是它会自动替换字符串中的变量:

1
2
3
4
var name = '小明';
var age = 20;
var message = `你好, ${name}, 你今年${age}岁了!`;
alert(message);

注: 模板字符串中回车换行也算字符长度。

toString

三种方法 ToString, 其中 nullundefined 进行 ToString 汇报错。

1
2
3
4
1+''
String(1)
(1).toString()
"1"

string 相关操作方法

  1. trim() 去掉调用字符串的前后空格。

  2. substring()返回指定索引区间的子串:

1
2
3
var s = 'hello, world'
s.substring(0, 5); // 从索引0开始到5(不包括5),返回'hello'
s.substring(7); // 从索引7开始到结束,返回'world'
  1. concat() 拼接两个字符串并返回新的字符串。同 Array。

4.slice() 裁切,同 Array 。

  1. repalce() 替换字符串中一个字符。

Boolean 布尔

每次看到 boolean 内心都不有的为布尔惋惜。俗话说:『老婆取得好,能够活到老啦』

ToBoolean

两种方式 ToBoolean:

1
2
3
Boolean('2')
!!"2"
true

五个 Falsy 值 :0,NaN,null, undefined, ''

Number 数字

ToNumber

五种方法进行 ToNumber 操作:

1
2
3
4
5
Number("1")
parseInt('1',10) // 第二个参数表示10进制
parseFloat('1.2')
"1" - 0
+ "1" // 取正

Object 对象

ToObject

访问属性是通过.操作符完成的,但这要求属性名必须是一个有效的变量名。如果属性名包含特殊字符,就必须用''括起来:

1
2
3
4
var xiaohong = {
name: '小红',
'middle-school': 'No.1 Middle School'
};

xiaohong的属性名middle-school不是一个有效的变量,就需要用''括起来。访问这个属性也无法使用.操作符,必须用['xxx']来访问:

1
2
3
xiaohong['middle-school']; // 'No.1 Middle School'
xiaohong['name']; // '小红'
xiaohong.name; // '小红'

也可以用xiaohong['name']来访问xiaohongname属性,不过xiaohong.name的写法更简洁。我们在编写JavaScript代码的时候,属性名尽量使用标准的变量名,这样就可以直接通过object.prop的形式访问一个属性了。

实际上JavaScript对象的所有属性都是字符串,不过属性对应的值可以是任意数据类型。

要判断一个属性是否是xiaoming自身拥有的,而不是继承得到的,可以用hasOwnProperty()方法:

1
2
3
4
5
var xiaoming = {
name: '小明'
};
xiaoming.hasOwnProperty('name'); // true
xiaoming.hasOwnProperty('toString'); // false

for循环的一个变体是for ... in循环,它可以把一个对象的所有属性依次循环出来:

1
2
3
4
5
6
7
8
var o = {
name: 'Jack',
age: 20,
city: 'Beijing'
};
for (var key in o) {
console.log(key); // 'name', 'age', 'city'
}

要过滤掉对象继承的属性,用hasOwnProperty()来实现:

1
2
3
4
5
6
7
8
9
10
var o = {
name: 'Jack',
age: 20,
city: 'Beijing'
};
for (var key in o) {
if (o.hasOwnProperty(key)) {
console.log(key); // 'name', 'age', 'city'
}
}

由于Array也是对象,而它的每个元素的索引被视为对象的属性,因此,for ... in循环可以直接循环出Array的索引:

1
2
3
4
5
var a = ['A', 'B', 'C'];
for (var i in a) {
console.log(i); // '0', '1', '2'
console.log(a[i]); // 'A', 'B', 'C'
}

请注意for ... inArray的循环得到的是String而不是Number

set map……..

遍历Array可以采用下标循环,遍历MapSet就无法使用下标。为了统一集合类型,ES6标准引入了新的iterable类型,ArrayMapSet都属于iterable类型。

具有iterable类型的集合可以通过新的for ... of循环来遍历。
注意区别 for in。 for in 不完善,仅限于对象输出。
然而,更好的方式是直接使用iterable内置的forEach方法,它接收一个函数,每次迭代就自动回调该函数。以Array为例:

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

如果对某些参数不感兴趣,由于JavaScript的函数调用不要求参数必须一致,因此可以忽略它们。例如,只需要获得Arrayelement

1
2
3
4
var a = ['A', 'B', 'C'];
a.forEach(function (element) {
console.log(element);
});

function

arguments最常用于判断传入参数的个数。你可能会看到这样的写法:

1
2
3
4
5
6
7
8
9
10
// 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变为默认值
}
// ...
}

ES6标准引入了rest参数,用于接收未定义的多余参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 ]

foo(1);
// 结果:
// a = 1
// b = undefined
// Array []

作用域

自定义全局作用域:
减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。例如:

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等等。

let

let 的作用:
由于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;
}

解构赋值

1
2
3
~~~
var [x, y, z] = ['hello', 'JavaScript', 'ES6'];
~~~

从一个对象中取出若干属性,也可以使用解构赋值,便于快速获取对象的指定属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

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是一致的。如果要使用的变量名和属性名不一致,可以用下面的语法获取:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school'
};

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

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

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

快速获取当前页面的域名和路径:

1
var {hostname:domain, pathname:path} = location;  // 重命名爲domain

如果一个函数接收一个对象作为参数,那么,可以使用解构直接把对象的属性绑定到变量中。例如,下面的函数可以快速创建一个Date对象:

1
2
3
function buildDate({year, month, day, hour=0, minute=0, second=0}) {
return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second);
}

它的方便之处在于传入的对象只需要yearmonthday这三个属性:

1
2
buildDate({ year: 2017, month: 1, day: 1 });
// Sun Jan 01 2017 00:00:00 GMT+0800 (CST)

也可以传入hourminutesecond属性:

1
2
buildDate({ year: 2017, month: 1, day: 1, hour: 20, minute: 15 });
// Sun Jan 01 2017 20:15:00 GMT+0800 (CST)

this

用一个that变量首先捕获this

var that = this;,你就可以放心地在方法内部定义其他函数,而不是把所有语句都堆到一个方法中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
'use strict';

var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var that = this; // 在方法内部一开始就捕获this
function getAgeFromBirth() {
var y = new Date().getFullYear();
return y - that.birth; // 用that而不是this
}
return getAgeFromBirth();
}
};

xiaoming.age(); // 25

arr.map() arr.reduce()

map()reducefilter()sort()、every()、find()、findIndex()、forEach()、

closure

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

obj

总结一下,有这么几条规则需要遵守:

  • 不要使用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'

number对象调用toString()报SyntaxError:

1
123.toString(); // SyntaxError

遇到这种情况,要特殊处理一下:

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

JavaScript的Date对象月份值从0开始,牢记0=1月,1=2月,2=3月,……,11=12月。

regExp

无法识别连续的空格,用正则表达式试试:

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

jason

统一解析,JSON的字符串规定必须用双引号"",Object的键也必须用双引号""
对象序列化成JSON格式的字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
'use strict';

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

要输出得好看一些,可以加上参数,按缩进输出:

1
JSON.stringify(xiaoming, null, '  ');

第二个参数用于控制如何筛选对象的键值,如果我们只想输出指定的属性,可以传入Array

1
JSON.stringify(xiaoming, ['name', 'skills'], '  ');

结果:

1
2
3
4
5
6
7
8
9
{
"name": "小明",
"skills": [
"JavaScript",
"Java",
"Python",
"Lisp"
]
}

反序列化

拿到一个JSON格式的字符串,我们直接用JSON.parse()把它变成一个JavaScript对象:

1
2
3
4
JSON.parse('[1,2,3,true]'); // [1, 2, 3, true]
JSON.parse('{"name":"小明","age":14}'); // Object {name: '小明', age: 14}
JSON.parse('true'); // true
JSON.parse('123.45'); // 123.45

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

1
2
3
4
5
6
7
8
'use strict';
var obj = JSON.parse('{"name":"小明","age":14}', function (key, value) {
if (key === 'name') {
return value + '同学';
}
return value;
});
console.log(JSON.stringify(obj)); // {name: '小明同学', age: 14}

DOM

js 最重要的任务是找到 DOM 上的 element,然后负责添加 css效果类。

选择器

集合选择器

1
document.querySelectorAll('\[data-x\]')

元素选择器

1
document.querySelector('a\[href="#'+ id +'"\]').parentNode