1、比较运算“==”和“===”区别:
“==”比较,他会自动转换数据类型再比较,很多时候会得到非常诡异的结果。
“===”比较,他不会自动转换数据类型,如果数据类型不一致,返回false。如果类型一致,再比较。
“==”比较是JavaScrpit早期语言设计存在设计缺陷只是为了兼容而保留,所以,现在应该尽可能使用“===”,而尽量避免使用“==”。
2、NaN是特殊的数值。
NaN与所有其他值都不相等,包括它自己,所以判断是否为NaN的方法是通过isNaN()函数:
NaN === NaN; //false
var a = NaN;
a === NaN; //false
isNaN(a); //true
3、数组索引以0为起始
4、JavaScript对象
var person = {
name: 'Bob',
age: 20,
tags: ['js', 'web', 'mobile'],
city: 'Beijing',
hasCar: true,
zipcode: null
};
访问对象属性使用“对象变量名.属性名”。
5、strict模式:强制要求使用var 申明变量。
在代码第一行写上:
'use strict';
严格模式为ES5后出现,所以,IE6,7,8,9 均不支持严格模式。
6、字符串可以使用'或者"括起来,如果字符串内同时包含'和"可以用转义字符\来标识。
转义字符\本身在字符串中也要转移所以,字符串中\\表示\,描述windows中的路径时要注意。
可以使用\x##形式表示ASCII字符,还可以用\u####表示一个Unicode字符:
'use strict';
var i = '\u4e2d\u6587\x41';
console.log(i);
ES6标准新增:
可以用反单引号括起可实现多行字符串
var a = `这是第一行
这是第二行
这是第三行`;
console.log(a);
/*
这是第一行
这是第二行
这是第三行
*/
模板字符串
var name = '小明';
var age = 20;
var message = `你好, ${name}, 你今年${age}岁了!`; //注意是反单引号
console.log(message);
7、操作字符串
.length
var s = 'Hello, world!';
s.length;
要获取字符串某个位置的支付可以使用类似数组下标的操作,索引号从0开始:
var s = 'Hello, world!';
s[0];
但是,这种方式不能用来修改字符串。
.toUpperCase()
字符串变大写字符串
var s = 'Hello';
s = s.toUpperCase(); // 返回'HELLO'
console.log(s);
.toLowerCase()
字符串变小写字符串
var s = 'Hello';
s = s.toLowerCase(); // 返回'hello'
console.log(s);
.indexOf()
指定字符串出现的位置,以0为起始,没找到返回-1;
var s = 'hello, world';
i1 = s.indexOf('world'); // 返回7
i2 = s.indexOf('World'); // 没有找到指定的子串,返回-1
console.log(`第一位置:${i1},第二位置:${i2}`);
.substring()
根据索引获取子串
var s = 'hello, world'
sub1 = s.substring(0, 5); // 从索引0开始到5(不包括5),返回'hello'
sub2 = s.substring(7); // 从索引7开始到结束,返回'world'
console.log(`第一:${sub1},第二:${sub2}`);
8、数组
数组可以包含任意数据类型,并通过索引来访问每个元素。
要取得数组的长度,直接访问length属性:
var arr = [1, 2, 3.14, 'Hello', null, ture];
arr.length; //6
注意,直接给数组的length赋一个新的值会导致数组大小的变化:
var arr = [1, 2, 3];
arr.length;
arr.length = 6;
arr; // arr变为[1, 2, 3, undefined, undefined, undefined]
arr.length = 2;
arr; //arr变为[1, 2]
大多数其他编程语言不允许直接改变数组的大小,越界访问索引会报错。然而,JavaScript的Array却不会有任何错误。在编写代码时,不建议直接修改数组的大小,访问索引时要确保索引不会越界。
.indexOf()
与String类似,数组也可以通过indexOf()来搜索一个指定的元素的位置:
var arr = [10, 20, '30', 'xyz'];
arr.indexOf(10); //元素10的索引为0
arr.indexOf(20); //元素20的索引为1
arr.indexOf(30); //元素30的没有找到,返回-1
arr.indexOf('30'); //元素'30'的索引为2
注意了,数字30和字符串'30'是不同的元素
.slice()
slice()就是对应String的substring()版本,它截取数组的部分元素,然后返回一个新的数组:
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']
如果不给slice()传递参数,他会从头到尾截取所有元素。利用这一点,我们可以很容易地复制一个数组。
var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
var aCopy = arr.slice();
aCopy; //['A', 'B', 'C', 'D', 'E', 'F', 'G']
aCopy === arr; //false
.push()和.pop()
push向数组的末尾添加一到多个元素,pop则把数组的最后一个元素删除掉:
var arr = [1, 2];
arr.push('A', 'B'); // 返回数组新的长度:4
arr; // [1, 2, 'A', 'B']
arr.pop(); // pop()返回'B'
arr; // [1, 2, 'A']
arr.pop(); arr.pop(); arr.pop() // 连续pop 3次
arr; // []
arr.pop(); // 空数组继续pop不会报错,而是返回undefined
arr; // []
.unshift()和.shift()
如果要往数组的头部添加若干元素,使用unshift()方法,shift()方法则把数组的第一个元素删掉。
var arr = [1, 2];
arr.unshift('A', 'B'); // 返回 Array新的长度:4
arr; // ['A', 'B', 1, 2]
arr.shift(); // 'A'
arr; // ['B', 1, 2]
arr.shift(); arr.shift(); arr.shift(); // 连续shift 3次
arr; //[]
arr.shift(); // 空数组继续shift不会报错,而是返回undefined
arr; // []
.sort()
sort可以对当前数组进行排序,它会直接修改当前数组的元素位置,直接调用时,按照默认顺序排序。
var arr = ['B', 'C', 'A'];
arr.sort();
arr; // ['A', 'B', 'C']
.reverse()
reverse把整个数组的元素反转
var arr = [1, 2, 3];
arr.reverse();
arr; //[3, 2, 1]
.splice()
splice方法是修改数组的万能方法,它可以从指定的索引开始删除若干元素,然后再从该位置添加若干元素
var arr = ['Microsoft', 'Apple', 'Yahoo', 'AOL', 'Excite', 'Oracle'];
// 从索引2开始删除3个元素,然后在索引2开始添加两个元素:
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']
// 在数组尾部追加
arr.splice(5, 0, 'Yahoo', 'AOL'); // 返回[]
arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle', 'Yahoo', 'AOL']
.concat()
concat方法把当前的数组和另外一个数据包括数组链接起来,并返回一个新的数组
var arr = ['A', 'B', 'C'];
var added = arr.concat([1, 2, 3]);
added; // ['A', 'B', 'C', 1, 2, 3]
arr; // ['A', 'B', 'C']
arr.concat(1, 2, [3, 4]); // ['A', 'B', 'C', 1, 2, 3, 4]
concat方法并没有修改当前数组, 而是返回了一个新的数组。
.join()
join方法是一个非常实用的方法,它把当前数组的每个元素都用指定的字符串连接起来,然后返回连接后的字符串
var arr = ['A', 'B', 'C', 1, 2, 3];
arr.join('-'); // 'A-B-C-1-2-3' 可见数组元素不是字符串将转换为字符串后再连接
9、多维数组
如果数组的某个元素又是一个数组,则可以形成多维数组
var arr = [[1, 2, 3], [400, 500, 600], '-'];
var x = arr[1][1];
x; // 500
10、对象
var xiaoming = {
name: '小明',
birth: 1990,
school: 'No.1 Middle School',
height: 1.70,
weight: 65,
score: null
};
注意,最末尾的键值对后面不要加','号。
JS用花括号定义一个对象,属性以键值对形式声明,用逗号隔开。可以动态的以xxx.newname = value的形式给对象添加属性。
属性名称推荐以变量名规则命名,不要包含特殊字符,'-'号也是特殊字符。访问不存在的属性,会返回undefined。
删除属性,delete
判断对象中是否有某个属性,可以用in,但是,in判断的属性不一定是属于该对象,也可能是继承得到的。如果判断属性是否是对象自身拥有的,可以使用hasOwnProperty()方法。
xiaoming.hasOwnProperty('name'); // true
xiaoming.hasOwnProperty('toString'); // false
11、条件判断
if(){
......
}else{
......
}
建议使用花括号,即使其中只有一条语句。
多行条件判断
if () {
......
} else if () {
......
} else {
......
}
if的判断表达式通常是以布尔值true或false来判断,但是也有用其他值得情况。
JavaScript把null、undefined、0、NaN和空字符串''视为false,其他值一概视为true。
12、循环
传统for循环
for(let i=1; i<=1000; i++){
......
}
如果for循环中的三个表达式都省略,则可以实现无限循环for(;;){...}。无限循环要用break语句才能退出。
for ... in循环
把一个对象的所有属性一次循环出来。
var o ={
name: 'Jack',
age: 20,
city: 'Beijing'
};
for (var key in o){
console.log(key); // 'name', 'age', 'city'
}
数组也是对象,所以当用for...in对数组使用的时候,每个元素的索引就是属性
var a = ['A', 'B', 'C'];
for (var i in a){
console.log(i);
console.log(a[i]);
}
注意,for...in 对数组的循环得到的是String而不是Number。
while循环和do...while循环
没事什么特别的,略。
13、Map和Set
ES6规范引入的数据类型。
Map是一组键值对,具有极快的查找速度。
var m=new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
m.get('Michael'); // 95
m.set('Bob', 77); // 一个键只对应一个值,当对已有键设置值时,新值会换掉旧值,Bob 的值变为 77
m.has('Adam'); // false,检查是否有键'Adam'
m.set('Adam',65); //不存在的键,自动增加
m.delete('Adam'); //删除键'Adam'
m.get('Adam'); // undefined
Set是一组键的集合,没有value。键不能重复,如果有重复会自动过滤。
var s1 = new Set([1, 2, 3, 4, 3, '3']);
s1; // [1, 2, 3, 4, '3'] 重复的3被过滤掉,注意数字3和字符串3不是重复项
s1.add(6); //增加新key
s1.delete(6);//删除key
14、iterable
ES6标准一如的新类型,Array、Map和Set都属于iterable类型。具有iterable类型的集合可以通过for ... of循环来遍历。
'use strict';
var a = [1, 2, 3];
for (var x of a){
console.log(x);
}
遍历iterable类型还可以使用内置的forEach方法。
var a = ['A', 'B', 'C'];
a.forEach(function(element, index, array) {
// element: 指向当前元素的值
// index: 指向当前索引
// array: 指向Array对象本身
console.log(element + ', index = ' + index);
}
15、函数
函数的形式 function 函数名(参数列表){......}
函数也是对象,可以赋值给变量,然后通过变量调用。
函数允许接收任意个参数。
关键字 arguments,是一个参数数组,在函数内部起作用,指向传入的所有参数,有length属性,可以通过下标访问。
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 ]
foo(1);
// 结果:
// a = 1
// b = undefined
// Array []
注意,函数中return语句要只占一行,否则可能会出现意想不到的错误。
function foo(){
if(2 > 1){
return // JavaScript引擎有一个在行末自动添加分号的机制,这里相当于return;
2;
}
}
foo();
//结果为undefined
16、变量作用域
如果一个变量在函数内部进行var声明,则改变量作用域为整个函数体,在函数体外不可引用改变量:
'use strict';
function foo() {
var x =1;
x = x + 1;
}
x = x + 2; // ReferenceError 无法在函数体外引用变量x
JavaScript的函数是可以嵌套的,因此,内部函数可以访问外部函数定义的变量,反过来不行:
'use strict';
function foo() {
var x = 1;
function bar() {
var y = x+1; // bar 可以访问foo的变量x
}
var z = y + 1; // ReferenceError foo不可以访问bar的变量y
}
如果内部函数和外部函数定义了同名变量,则内部函数中变量会屏蔽外部函数变量。
变量提升
JavaScript的函数在使用时,会先扫描整个函数体的语句,把所有声明的变量“提升”到函数顶部:
'use strict';
function foo() {
var x = 'Hello, '+y; // 因为经过扫描,JavaScript知道y已经声明了,所以不会报错,但是此时y是没有值,是undefined
console.log(x);
var y = 'Bob';
}
foo();
所以上述的foo函数,相当于:
function foo() {
var x,y;
x = 'Hello, '+y;
console.log(x);
y = 'Bob';
}
因为JavaScript有这样的特性,所以我们在函数内部定义变量时,应该模仿pascal在函数中先声明所有的变量,在进行使用。
全局作用域
不在任何函数内定义的变量就具有全局作用域。JavaScript默认有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性:
'use strict';
var course = 'Learn JavaScript';
alert(course); // 'Learn JavaScript'
alert(window.course); // 与上一句是一样的
顶层函数实际上也是一个全局变量并绑定到window对象:
'use strict';
function foo() {
alert('foo');
}
foo();
window.foo();
JavaScript默认的函数其实也是window的一个变量,我们可以通过修改变量达到重新定义默认函数的目的:
'use strict';
var old_alert = window.alert;
window.alert = function() {}
alert('无法用alert()显示了');
//恢复alert;
window.alert = old_alert;
alert('又可以用alert()了');
名字空间
全局变量会绑定到window上,但是我们在网页中常常使用多个JavaScript文件,如果这些文件中使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,而且很难被发现。
所以,为了减少冲突,我们可以吧自己的所有变量和函数全部绑定到一个全局变量中。例如:
var MYAPP = {};
MYAPP.name = 'myapp';
MYAPP.version = 1.0;
MYAPP.foo = function() {
return 'foo';
};
这样可以大大减少全局变量冲突的可能性。
局部作用域
局部作用域上是整个函数内部,而有时需要声明块级作用域的变量。ES6引入了新关键字let来实现:
'use strict';
function foo() {
var sum = 0;
for (let i=0; i<100; i++) {
sum += i;
}
i += 1; // SyntaxError: i再for块声明,所以此处错误。如果是var则不会出错。
}
常量
由于var和lett声明的是变量,如果要声明一个常量,在ES6之前是不行的,以往通常用全部大写的变量来表示这是常量。
ES6标准引入了新的关键字const来定义常量,const与let都具有块级作用域:
'use strict';
const PI = 3.14;
PI = 3; // 某些浏览器不报错,但是无效果
PI; // 3.14
17、解构赋值
从ES6开始,JavaScript引入了解构赋值,可以同时对一组变量进行赋值。
解构赋值就是直接对多个变量同时赋值:
'use strict';
// 如果浏览器支持解构赋值就不会报错:
var [x, y, z] = ['hello', 'JavaScript', 'ES6'];
// x, y, z分别被赋值为数组对应元素:
console.log('x = ' + x + ', y = ' + y + ', z = ' + z);
如果数组本身还有嵌套,也可以通过以下的形式进行解构赋值。
let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
x; // 'hello'
y; // 'JavaScript'
z; // 'ES6'
解构赋值还可以忽略某些元素:
let [ , ,z] = ['hello', 'JavaScript', 'ES6']; // 忽略前两个元素, 只对z赋值第三个元素
z; // 'ES6'
如果需要从一个对象中取出若干属性, 也可以使用解构赋值,便于快速获取对象的指定属性:
'use strict';
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, age, address: {city, zip}} = person;
var {passport: id} = person; // 将属性passport赋值给变量id
// 分别被赋值为对应属性,变量名与属性名相同
// address 不是变量,而是为了让city和zip获得嵌套的address对象的属性
// passport 不是变量,而是为了让变量id获得passport属性
name; // '小明'
age; // 20
city; // 'Beijing'
zip; // undefined,因为属性名是zipcode而不是zip
address; // Uncaught ReferenceError: address is not defined
id; // 'G-12345678'
passport; // Uncaught ReferenceError: passport is not defined
解构赋值还可以使用默认值,这样就避免了不存在的属性返回undefined的问题:
var {name, single=true} = person;
name; // '小明'
single; // true
当变量已经声明后再进行解构赋值时要注意,应该使用小括号括起来,否则花括号会被当做块处理而引发错误。
var x, y;
{x, y} = {name: '小明', x: 100, y: 200}; // 引发语法错误: Uncaught SyntaxError: Unexpected token =
({x, y} = {name: '小明', x: 100, y: 200}); // 正确
使用场景
解构赋值在很多时候可以简化代码。例如,交换两个变量x和y的值,可以这么写, 不再需要临时变量:
var x=1, y=2;
[x, y] = [y, x];
快速获取当前页面的域名和路径:
var {hostname: domain, pathname: path} = location;
如果一个函数接收一个对象作为参数,那么,可以使用解构直接把对象的属性绑定到变量中。例如:
function buildDate({year, month, day, hour=0, minute=0, second=0}) {
return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second);
}
buildDate({year: 2017, month: 1, day: 1});
18、方法
在对象中绑定的函数,被称为这个对象的方法。
var xiaoming = {
name: '小明',
birth: 1990,
age: function() {
var y = new Date().getFullYear();
return y - this.birth;
}
};
xiaoming.age; // [Function: age] 这样不会调用要加上小括号
xiaoming.age(); // 根据调用时的日期计算当前年龄
方法和普通函数没有太多区别。
this 关键字
在一个方法内部,this是一个特殊变量,它始终指向当前对象,也就是xiaoming这个变量。所以,可以使用tihs获取当前对象的属性,或者调用方法。
但是,当方法函数放在对象外部,或者是嵌套在方法函数中时,this根据调用情况会有所不同。看下面示例:
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 获得正常结果
getAge(); // NaN, 此时,this指向window对象,window此时没有birth属性、
var fn = xiaoming.age();
fn(); // NaN,只有用“对象.方法()”调用时,this才会指向正确的对象
注意,ES5之后在strict模式下,函数中的this不在指向window而是指向undefined。此时,直接调用使用了this的函数时,会引发TypeError: this is undefined 错误。当方法中嵌套的函数中要使用对象的属性时,应该用一个that变量先捕获this,然后再在嵌套函数中使用that变量:
'use strict';
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var that = this; // 在方法内部一开始就捕获this
function getAgeFromBirth() {
var y = new Date().getFullYear();
return y - that.birth;
}
return getAgeFromBirth();
}
};
xiaoming.age() // 28
apply方法
函数的apply方法可以使函数中的this绑定到指定的对象。
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
};
getAge.apply(xiaoming, []); // 28, this 指向xiaoming, 参数为空
另一个与apply()类似的方法是call(),区别是:
apply()把参数打包成数组再传入;
call()把参数按顺序传入。
比如调用Math.max(3, 5, 4), 分别用apply() 和 call() 实现如下:
Math.max.apply(null, [3, 5, 4]); //5
Math.max.call(null, 3, 5, 4); // 5
对于普通函数调用,我们通常把this绑定为null。
装饰器
利用apply(),我们还可以动态改变函数的行为。
JavaScript 的所有对象都是动态的,即使内置的函数,我们也可以重新指向新的函数。
例如:假定我们要统计一下代码一共调用了多少次parseInt()函数。
'use strict';
var count = 0;
var oldParseInt = parseInt; // 保存原函数
window.parseInt = function() {
count +=1;
return oldParseInt.apply(null, arguments); // 调用原函数
};
parseInt('10');
parseInt('20');
parseInt('30');
console.log('count = ' + count); //3
19、高阶函数(Higher-order function)
JavaScript的函数其实都绑定到某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
一个最简单的高阶函数:
function add(x, y, f) {
return f(x) + f(y);
}
当我们调用add(-5, 6, Math.abs)时,参数x, y 和f分别接收-5, 6 和函数Math.abs,根据函数定义,我们可以推导计算过程为:
x = -5;
y = 6;
f = Math.abs;
f(x) + f(y) ==> Math.abs(-5) + Math.abs(6) ==> 11;
return 11;
map/reduce
map用于对数组的每一个元素进行处理
举例,比如我们有一个函数f(x) = x^2,要把这个函数作用在一个数组[1, 2, 3, 4, 5, 6, 7, 8, 9] 上, 就可以用map实现如下:
'use strict';
function pow(x) {
return x * x;
}
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var results = arr.map(pow);
console.log(results); // [1, 4, 9, 16, 25, 36, 49, 64, 81]
reduce
reduce接收一个函数作为参数,函数有两个参数,效果如下:
function f(x,y){
......
}
[x1, x2, x3, x4 ,x5].reduce(f) = f(f(f(f(x1,x2),x3),x4),x5);
filter
用于把数组中的某些元素过滤掉,然后返回剩下的元素。
和map()类似,filter()也接收一个函数。filter()把传入的函数一次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃改元素。
例如,在一个数组中删除掉偶数,只保留奇数,可以这样写:
var arr = [1, 2, 3, 4, 5, 6, 9, 10, 15];
var r = arr.filter(function(x){
return x % 2 !== 0;
});
r; // [1, 5, 9, 15]
把一个数组中的空字符串删掉, 可以这么写:
var arr = ['A', '', 'B', null, undefined, 'C', ' '];
var r = arr.filter(function(s) {
return s && s.trim(); //注意,IE9以下的版本没有trim()方法
});
r; // ['A', 'B', 'C']
回调函数
filter() 接收的回调函数,其实可以有多个参数。通常我们仅适用第一个参数,表示数组的某个元素。
回调函数汉可以接收另外两个参数,表示元素的位置和数组本身:
//去掉重复元素
var arr = ['A', 'B', 'C', 'A', 'D', 'C', 'E'];
var r = arr.filter(function (element, index, self) {
return self.indexOf(element) === index; //查询当前元素位置,如果位置和当前位置相等则表示非重复元素
});
sort排序
数组的sort()方法排序,可能会得到意想不到的结果:
['Goolge', 'Apple', 'Microsoft'].sort(); // ['Apple', 'Google', 'Microsoft']
['Google', 'apple', 'Microsoft'].sort(); // ['Google', 'Microsoft', 'apple']
[10, 20, 1, 2].sort(); // [1, 10, 2, 20]
可以看出,字符串时按ASCII顺序进行排序的,而数字排序也都先转换成字符串后再进行排序。
sort也是高阶函数,所以我们可以传递一个比较函数来实现自定义的排序。
例如,按数字大小排序:
'use strict';
var arr = [10, 20, 1, 2];
arr.sort(function(x, y) {
if (x < y) {
return -1; //小于返回-1
}
if (x > y) {
return 1; //大于返回1
}
return 0; // 等于返回0
});
console.log(arr); // [1, 2, 10, 20]
注意,sort()方法会直接对调用的数组进行修改,它返回结果仍然是当前数组。
20、闭包
函数作为返回值