[快速入门]
....
【`:反引号】。var name ='小明';
var message = `你好,${name}`;//注意:用的是反引号
alert(message);
var s = 'Hello,World';
s.toUpperCase();//HELLO,WORLD
s.toLowerCase();//hello,world
s.indexOf('world');//没有找到子串,返回-1
s.indexOf('World');//返回 7
s.substring(0,5);//返回'Hello'
s.substring(7);//从索引7开始到结束,返回'World'
var arr = [1, 2, 3.14, 'Hello', null, true];
arr.length;//6
arr.length = 8;
arr; // arr变为 [1, 2, 3.14, 'Hello', null, true, undefined, undefined]
arr.length = 2;
arr; // arr变为[1, 2]
arr[1] = 99;
arr; // arr现在变为['1', 99]
arr[5] = 'x';
arr; // arr变为['1', 99 , undefined, undefined, 'x']
arr.indexOf(99); // 元素10的索引为1
arr.indexOf('x'); // 元素'x'的索引为4
arr.indexOf(1); // 元素1没有找到,返回-1
arr.indexOf('1'); // 元素'1'的索引为0
arr.slice(0, 3); // 从索引0开始,到索引3结束,但不包括索引3: ['1', 99 , undefined]
arr.slice(3); // 从索引3开始到结束: [undefined, 'x']
arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
var aCopy = arr.slice();
aCopy; // ['A', 'B', 'C', 'D', 'E', 'F', 'G']
aCopy === arr; // false
arr = [1, 2];
arr.push('A', 'B'); // 返回Array新的长度: 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; // []
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; // []
arr = ['B', 'C', 'A'];
arr.sort();
arr; // ['A', 'B', 'C']
arr = ['one', 'two', 'three'];
arr.reverse();
arr; // ['three', 'two', 'one']
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']
arr = ['A', 'B', 'C'];
var added = arr.concat([1, 2, 3]);
added; // ['A', 'B', 'C', 1, 2, 3]
arr; // ['A', 'B', 'C']
arr = ['A', 'B', 'C'];
arr.concat(1, 2, [3, 4]); // ['A', 'B', 'C', 1, 2, 3, 4]
arr = ['A', 'B', 'C', 1, 2, 3];
arr.join('-'); // 'A-B-C-1-2-3'
arr = [[1, 2, 3], [400, 500, 600], '-'];
//Array提供了一种顺序存储一组元素的功能,并可以按索引来读写。
//练习:在新生欢迎会上,你已经拿到了新同学的名单,请排序后显示:欢迎XXX,XXX,XXX和XXX同学!:
'use strict';
var arr = ['小明', '小红', '大军', '阿黄'];
//arr.sort();
//console.log(`欢迎${arr[0]},${arr[1]},${arr[2]}和${arr[3]}同学!`);
arr.push(`${arr.sort().pop()}同学!`);
arr.push(arr.splice(2,2).join('和'));
arr.unshift(`欢迎${arr.shift()}`);
console.log(arr.join(","));
var o = {
name: 'Jack',
age: 20,
city: 'Beijing'
};
for (var key in o) {
console.log(key); // 'name', 'age', 'city'
}
//要过滤掉对象继承的属性,用hasOwnProperty()来实现:
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的索引:
var a = ['A', 'B', 'C'];
for (var i in a) {
console.log(i); // '0', '1', '2'
console.log(a[i]); // 'A', 'B', 'C'
}//请注意,for ... in对Array的循环得到的是String而不是Number。
var x = 0;
var n = 99;
while (n > 0) {
x = x + n;
n = n - 2;
}
x; // 2500
var n = 0;
do {
n = n + 1;
} while (n < 100);
n; // 100//用do { ... } while()循环要小心,循环体会至少执行1次,而for和while循环则可能一次都不执行。
'use strict';
var m = new Map();
var s = new Set();
console.log('你的浏览器支持Map和Set!');
///
var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
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
//
var s1 = new Set(); // 空Set
var s2 = new Set([1, 2, 3]); // 含1, 2, 3
var s = new Set([1, 2, 3, 3, '3']);
s; // Set {1, 2, 3, "3"}
s.add(4);
s; // Set {1, 2, 3, 4}
s.add(4);
s; // 仍然是 Set {1, 2, 3, 4}
var s = new Set([1, 2, 3]);
s; // Set {1, 2, 3}
s.delete(3);
s; // Set {1, 2}
var a = ['A', 'B', 'C'];
var s = new Set(['A', 'B', 'C']);
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
for (var x of a) { // 遍历Array
console.log(x);
}
for (var x of s) { // 遍历Set
console.log(x);
}
for (var x of m) { // 遍历Map
console.log(x[0] + '=' + x[1]);
}
//forEach的使用
var a = ['A', 'B', 'C'];
a.forEach(function (element, index, array) {
// element: 指向当前元素的值
// index: 指向当前索引
// array: 指向Array对象本身
console.log(element + ', index = ' + index);
});
var s = new Set(['A', 'B', 'C']);
s.forEach(function (element, sameElement, set) {
console.log(element);
});
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
m.forEach(function (value, key, map) {
console.log(value);
});
函数
// 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变为默认值
}
// ...
}
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 []
function sum(...rest){
var a = 0 ;
rest.forEach(function(e){a+=e})
return a;
}
// 唯一的全局变量MYAPP:
var MYAPP = {};
// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;
// 其他函数:
MYAPP.foo = function () {
return 'foo';
};
'use strict';
const PI = 3.14;
PI = 3; // 某些浏览器不报错,但是无效果!
PI; // 3.14
function foo() {
var sum = 0;
for (let i=0; i<100; i++) {
sum += i;
}
// SyntaxError:
i += 1;
}
'use strict';
//也可以使用解构赋值,便于快速获取对象的指定属性
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
在一个方法内部,this是一个特殊变量,它始终指向当前对象,也就是xiaoming这个变量。所以,this.birth可以拿到xiaoming的birth属性。
new Date().getFullYear();
如果以对象的方法形式调用,比如xiaoming.age(),该函数的this指向被调用的对象,也就是xiaoming,这是符合我们预期的。如果单独调用函数,比如getAge(),此时,该函数的this指向全局对象,也就是window。
'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
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 25
getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空
另一个与apply()类似的方法是call(),唯一区别是:
apply()把参数打包成Array再传入;
call()把参数按顺序传入。
比如调用Math.max(3, 5, 4),分别用apply()和call()实现如下:
Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5
利用apply(),我们还可以动态改变函数的行为。
JavaScript的所有对象都是动态的,即使内置的函数,我们也可以重新指向新的函数。
现在假定我们想统计一下代码一共调用了多少次parseInt(),可以把所有的调用都找出来,然后手动加上count += 1,不过这样做太傻了。最佳方案是用我们自己的函数替换掉默认的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
- [x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)
'use strict';
//利用reduce()求积
function product(arr) {
return arr.reduce(function(x,y){return x*=y;}) ;
}
测试失败 console.log
//失败
var s = '123123';
var arr = s.split('');
var index = function (n) {
var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
numbers.forEach(function (e) {
if ('' + e + '' === n) return e;
})
}
var arr1 = arr.map(index);
return arr1.reduce(function (x, y) {
return x * 10 + y;
});
//通过
var arr = s.split('');
var index = function(n){
var numbers=[1,2,3,4,5,6,7,8,9,0];
var tmp = '';
numbers.forEach(function(e){
if(''+e+'' === n) tmp = e;});
return tmp;
}
arr = arr.map(index);
return arr.reduce(function (x,y){return x*10+y;});
箭头函数
x => x * x
相当于 function (x) {return x * x;}
x => {
if (x > 0) {
return x * x;
}
else {
return - x * x;
}
}
//如果参数不是一个,就需要用括号()括起来:
// 两个参数:
(x, y) => x * x + y * y
// 无参数:
() => 3.14
// 可变参数:
(x, y, ...rest) => {
var i, sum = x + y;
for (i=0; i<rest.length; i++) {
sum += rest[i];
}
return sum;
}
//如果要返回一个对象,就要注意,如果是单表达式要改为:
// ok:
x => ({ foo: x })
更细心的同学指出,number对象调用toString()报SyntaxError:
123.toString(); // SyntaxError
遇到这种情况,要特殊处理一下:
123…toString(); // ‘123’, 注意是两个点!
(123).toString(); // ‘123’
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的Date对象月份值从0开始,牢记0=1月,1=2月,2=3月,……,11=12月。
第二种创建一个指定日期和时间的方法是解析一个符合ISO 8601格式的字符串:
var d = Date.parse('2015-06-24T19:49:22.875+08:00');
d; // 1435146562875
但它返回的不是Date对象,而是一个时间戳。不过有时间戳就可以很容易地把它转换为一个Date:
var d = new Date(1435146562875);
d; // Wed Jun 24 2015 19:49:22 GMT+0800 (CST)
d.getMonth(); // 5
使用Date.parse()时传入的字符串使用实际月份0112,转换为Date对象后getMonth()获取的月份值为011。
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小时
在正则表达式中,如果直接给出字符,就是精确匹配。用\d可以匹配一个数字,\w可以匹配一个字母或数字
.可以匹配任意字符
要匹配变长的字符,在正则表达式中,用*表示任意个字符(包括0个),用+表示至少一个字符,用?表示0个或1个字符,用{n}表示n个字符,用{n,m}表示n-m个字符
var re1 = /ABC\-001/;
var re2 = new RegExp('ABC\\-001');
re1; // /ABC\-001/
re2; // /ABC\-001/
//无法识别连续的空格,用正则表达式试试:
'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']
var re = /^(\d+)(0*)$/;
re.exec('102300'); // ['102300', '102300', '']
var r1 = /test/g;
// 等价于:
var r2 = new RegExp('test', 'g');
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,直到结束仍没有匹配到
'use strict';
//邮箱正则表达式
var re = /^[a-zA-Z\_\$][0-9a-zA-Z\_\.\$]*\@[0-9a-zA-Z\_\$]*\.[a-zA-Z\$]/;
// 测试:
var
i,
success = true,
should_pass = ['[email protected]', '[email protected]', '[email protected]', '[email protected]'],
should_fail = ['test#gmail.com', 'bill@microsoft', 'bill%[email protected]', '@voyager.org'];
for (i = 0; i < should_pass.length; i++) {
if (!re.test(should_pass[i])) {
console.log('测试失败: ' + should_pass[i]);
success = false;
break;
}
}
for (i = 0; i < should_fail.length; i++) {
if (re.test(should_fail[i])) {
console.log('测试失败: ' + should_fail[i]);
success = false;
break;
}
}
if (success) {
console.log('测试通过!');
}
JSON
""
,Object的键也必须用双引号""
,json的序列化函数:stringify(),JSON.stringify(xiaoming, null, ’ ');'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);
JSON.stringify(xiaoming, ['name', 'skills'], ' ');
xiaoming ={
"name": "小明",
"skills": [
"JavaScript",
"Java",
"Python",
"Lisp"
]
}
function convert(key, value) {
if (typeof value === 'string') {
return value.toUpperCase();
}
return value;
}
JSON.stringify(xiaoming, convert, ' ');
xiaoming ={
"name": "小明",
"age": 14,
"gender": true,
"height": 1.65,
"grade": null,
"middle-school": "\"W3C\" MIDDLE SCHOOL",
"skills": [
"JAVASCRIPT",
"JAVA",
"PYTHON",
"LISP"
]
}
//如果我们还想要精确控制如何序列化小明,可以给xiaoming定义一个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格式的字符串,我们直接用JSON.parse()把它变成一个JavaScript对象:
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
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}
Student
类来表示学生,类本身是一种类型,Student
表示学生类型,但不表示任何具体的某个学生;Student
类可以创建出xiaoming、xiaohong、xiaojun等多个实例,每个实例表示一个具体的学生,他们全都属于Student
类型。var Student = {
name: 'Robot',
height: 1.2,
run: function () {
console.log(this.name + ' is running...');
}
};
var xiaoming = {
name: '小明'
};
xiaoming.__proto__ = Student;
注意最后一行代码把xiaoming的原型指向了对象Student,看上去xiaoming仿佛是从Student继承下来的
在JavaScrip代码运行时期,你可以把xiaoming从Student变成Bird,或者变成任何对象。
// 原型对象:
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对每个创建的对象都会设置一个原型,指向它的原型对象。
当我们用obj.xxx访问一个对象的属性时,JavaScript引擎先在当前对象上查找该属性,如果没有找到,就到其原型对象上找,如果还没有找到,就一直上溯到Object.prototype对象,最后,如果还没有找到,就只能返回undefined。
构造函数:除了直接用{ … }创建一个对象外,JavaScript还可以用一种构造函数的方法来创建对象。它的用法是,先定义一个构造函数:
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
,也就是说,不需要在最后写return this
;。
xiaoming ----> Student.prototype ----> Object.prototype ----> null
//class的定义包含了构造函数constructor和定义在原型对象上的函数hello()(注意没有function关键字),这样就避免了Student.prototype.hello = function () {...}这样分散的代码。
class Student {
constructor(name) {
this.name = name;
}
hello() {
alert('Hello, ' + this.name + '!');
}
}
class
定义对象的另一个巨大的好处是继承更方便了。想一想我们从Student派生一个PrimaryStudent需要编写的代码量。现在,原型继承的中间对象,原型对象的构造函数等等都不需要考虑了,直接通过extends
来实现:class PrimaryStudent extends Student {
constructor(name, grade) {
super(name); // 记得用super调用父类的构造方法!
this.grade = grade;
}
myGrade() {
alert('I am at grade ' + this.grade);
}
}
注意PrimaryStudent的定义也是class
关键字实现的,而extends
则表示原型链对象来自Student。子类的构造函数可能会与父类不太相同,例如,PrimaryStudent需要name和grade两个参数,并且需要通过super(name)
来调用父类的构造函数,否则父类的name
属性无法正常初始化。
PrimaryStudent已经自动获得了父类Student的hello方法,我们又在子类中定义了新的myGrade方法。
ES6引入的class和原有的JavaScript原型继承有什么区别呢?实际上它们没有任何区别,class
的作用就是让JavaScript引擎去实现原来需要我们自己编写的原型链代码。简而言之,用class
的好处就是极大地简化了原型链代码。
你一定会问,class这么好用,能不能现在就用上?
现在用还早了点,因为不是所有的主流浏览器都支持ES6
的class
。如果一定要现在就用上,就需要一个工具把class
代码转换为传统的prototype
代码,可以试试Babel
这个工具。
//练习
'use strict';
class Animal {
constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name) {
super(name);
}
say() {
return 'Hello, ' + this.name + '!';
}
}
// 测试:
var kitty = new Cat('Kitty');
var doraemon = new Cat('哆啦A梦');
if ((new Cat('x') instanceof Animal) && kitty && kitty.name === 'Kitty' && kitty.say && typeof kitty.say === 'function' && kitty.say() === 'Hello, Kitty!' && kitty.say === doraemon.say) {
console.log('测试通过!');
} else {
console.log('测试失败!');
}
浏览器
IE
浏览器,历来对W3C标准支持差。从IE10
开始支持ES6标准;Google
出品的基于Webkit
内核浏览器,内置了非常强悍的JavaScript引擎——V8
。由于Chrome一经安装就时刻保持自升级,所以不用管它的版本,最新版早就支持ES6
了;Apple
的Mac系统自带的基于Webkit
内核的浏览器,从OS X 10.7 Lion
自带的6.1版本开始支持ES6
,目前最新的OS X 10.11 El Capitan
自带的Safari
版本是9.x
,早已支持ES6
;Mozilla
自己研制的Gecko
内核和JavaScript
引擎OdinMonkey
。早期的Firefox
按版本发布,后来终于聪明地学习Chrome
的做法进行自升级,时刻保持最新;iOS
和Android
两大阵营分别主要使用Apple
的Safari
和Google的Chrome
,由于两者都是Webkit
核心,结果HTML5
首先在手机上全面普及(桌面绝对是Microsoft
拖了后腿),对JavaScript
的标准支持也很好,最新版本均支持ES6。其他浏览器如Opera
等由于市场份额太小就被自动忽略了。
另外还要注意识别各种国产浏览器,如某某安全浏览器,某某旋风浏览器,它们只是做了一个壳,其核心调用的是IE
,也有号称同时支持IE
和Webkit
的“双核”浏览器。
不同的浏览器对JavaScript
支持的差异主要是,有些API
的接口不一样,比如AJAX,File接口
。对于ES6标准,不同的浏览器对各个特性支持也不一样。
在编写JavaScript的时候,就要充分考虑到浏览器的差异,尽量让同一份JavaScript代码能运行在不同的浏览器中。
window
对象不但充当全局作用域,而且表示浏览器窗口。
window
对象有innerWidth
和innerHeight
属性,可以获取浏览器窗口的内部宽度和高度。内部宽高是指除去菜单栏、工具栏、边框等占位元素后,用于显示网页的净宽高。兼容性:IE<=8不支持。
对应的,还有一个outerWidth
和outerHeight
属性,可以获取浏览器窗口的整个宽高。
navigator对象表示浏览器的信息,最常用的属性包括:
console.log('appName = ' + navigator.appName);
console.log('appVersion = ' + navigator.appVersion);
console.log('language = ' + navigator.language);
console.log('platform = ' + navigator.platform);
console.log('userAgent = ' + navigator.userAgent);
undefined
的特性,直接用短路运算符||
计算:var width = window.innerWidth || document.body.clientWidth;
screen对象表示屏幕的信息,常用的属性有:
location
对象表示当前页面的URL信息。例如,一个完整的URL:http://www.example.com:8080/path/index.html?a=1&b=2#TOP
,可以用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()。如果要重新加载当前页面,调用location.reload()方法非常方便。
'use strict';
if (confirm('重新加载当前页' + location.href + '?')) {
location.reload();
} else {
location.assign('/'); // 设置一个新的URL地址
}
document
对象表示当前页面。由于HTML
在浏览器中以DOM
形式表示为树形结构,document
对象就是整个DOM
树的根节点。
document
的title
属性是从HTML
文档中的
读取的,但是可以动态改变:'use strict';document.title = 'JavaScript';
要查找DOM树的某个节点,需要从document
对象开始查找。最常用的查找是根据ID
和Tag Name
。
用document
对象提供的getElementById()
和getElementsByTagName()
可以按ID
获得一个DOM
节点和按Tag
名称获得一组DOM
节点:
<dl id="drink-menu" style="border:solid 1px #ccc;padding:6px;">
<dt>摩卡dt>
<dd>热摩卡咖啡dd>
<dt>酸奶dt>
<dd>北京老酸奶dd>
<dt>果汁dt>
<dd>鲜榨苹果汁dd>
dl>
var menu = document.getElementById('drink-menu');
var drinks = document.getElementsByTagName('dt');
var i, s, menu, drinks;
menu = document.getElementById('drink-menu');
menu.tagName; // 'DL'
drinks = document.getElementsByTagName('dt');
s = '提供的饮料有:';
for (i=0; i<drinks.length; i++) {
s = s + drinks[i].innerHTML + ',';
}
console.log(s);
//提供的饮料有:摩卡,酸奶,果汁,
document对象还有一个cookie属性,可以获取当前页面的Cookie。
Cookie是由服务器发送的
key-value
标示符。因为HTTP
协议是无状态的,但是服务器要区分到底是哪个用户发过来的请求,就可以用Cookie
来区分。当一个用户成功登录后,服务器发送一个Cookie
给浏览器,例如user=ABC123XYZ(加密的字符串)...
,此后,浏览器访问该网站时,会在请求头附上这个Cookie
,服务器根据Cookie
即可区分出用户。
Cookie
还可以存储网站的一些设置,例如,页面显示的语言等等。
JavaScript
可以通过document.cookie
读取到当前页面的Cookie
:
document.cookie; // 'v=123; remember=true; prefer=zh'
如果引入的第三方的
JavaScript
中存在恶意代码,则www.foo.com网站将直接获取到www.example.com网站的用户登录信息。为了解决这个问题,服务器在设置
Cookie
时可以使用httpOnly
,设定了httpOnly
的Cookie
将不能被JavaScript
读取。这个行为由浏览器实现,主流浏览器均支持httpOnly
选项,IE
从IE6 SP1
开始支持。为了确保安全,服务器端在设置
Cookie
时,应该始终坚持使用httpOnly
。
history
对象保存了浏览器的历史记录,JavaScript
可以调用history
对象的back()
或forward ()
,相当于用户点击了浏览器的“后退”或“前进”按钮。
这个对象属于历史遗留对象,对于现代Web
页面来说,由于大量使用AJAX
和页面交互,简单粗暴地调用history.back()
可能会让用户感到非常愤怒。
新手开始设计Web
页面时喜欢在登录页登录成功时调用history.back()
,试图回到登录前的页面。这是一种错误的方法。
由于HTML文档被浏览器解析后就是一棵DOM树,要改变HTML的结构,就需要通过JavaScript来操作DOM。
始终记住DOM是一个树形结构。操作一个DOM节点实际上就是这么几个操作:
在操作一个DOM
节点前,我们需要通过各种方式先拿到这个DOM
节点。最常用的方法是document.getElementById()
和document.getElementsByTagName()
,以及CSS
选择器document.getElementsByClassName()
。
由于ID
在HTML
文档中是唯一的,所以document.getElementById()
可以直接定位唯一的一个DOM
节点。document.getElementsByTagName()
和document.getElementsByClassName()
总是返回一组DOM
节点。要精确地选择DOM
,可以先定位父节点,再从父节点开始选择,以缩小范围。例如:
// 返回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()
和querySelectorAll()
,需要了解selector
语法,然后使用条件来获取节点,更加方便:
// 通过querySelector获取ID为q1的节点:
var q1 = document.querySelector('#q1');
// 通过querySelectorAll获取q1节点内的符合条件的所有节点:
var ps = q1.querySelectorAll('div.highlighted > p');
注意:低版本的
IE<8
不支持querySelector
和querySelectorAll
。IE8
仅有限支持。
严格地讲,我们这里的DOM
节点是指Element
,但是DOM
节点实际上是Node
,在HTML
中,Node
包括Element
、Comment
、CDATA_SECTION
等很多种,以及根节点Document
类型,但是,绝大多数时候我们只关心Element
,也就是实际控制页面结构的Node
,其他类型的Node
忽略即可。根节点Document
已经自动绑定为全局变量document
。
<div id="test-div">
<div class="c-red">
<p id="test-p">JavaScriptp>
<p>Javap>
div>
<div class="c-red c-green">
<p>Pythonp>
<p>Rubyp>
<p>Swiftp>
div>
<div class="c-green">
<p>Schemep>
<p>Haskellp>
div>
div>
'use strict';
// 选择JavaScript
:
var js = document.querySelector('#test-p');
// 选择Python
,Ruby
,Swift
:
var arr = document.querySelectorAll('div.c-red.c-green > p');
// 选择Haskell
:
var haskell = document.querySelectorAll('div.c-green')[1].lastElementChild;
// 测试:
if (!js || js.innerText !== 'JavaScript') {
alert('选择JavaScript失败!');
} else if (!arr || arr.length !== 3 || !arr[0] || !arr[1] || !arr[2] || arr[0].innerText !== 'Python' || arr[1].innerText !== 'Ruby' || arr[2].innerText !== 'Swift') {
console.log('选择Python,Ruby,Swift失败!');
} else if (!haskell || haskell.innerText !== 'Haskell') {
console.log('选择Haskell失败!');
} else {
console.log('测试通过!');
}
可以直接修改节点的文本,方法有两种:
一种是修改innerHTML
属性,这个方式非常强大,不但可以修改一个DOM
节点的文本内容,还可以直接通过HTML
片段修改DOM
节点内部的子树:
// 获取...
var p = document.getElementById('p-id');
// 设置文本为abc:
p.innerHTML = 'ABC'; // ABC
// 设置HTML:
p.innerHTML = 'ABC RED XYZ';
// ...
的内部结构已修改
用innerHTML
时要注意,是否需要写入HTML
。如果写入的字符串是通过网络拿到了,要注意对字符编码来避免XSS
攻击。
第二种是修改innerText或textContent属性,这样可以自动对字符串进行HTML编码,保证无法设置任何HTML标签:
// 获取...
var p = document.getElementById('p-id');
// 设置文本:
p.innerText = '';
// HTML被自动编码,无法设置一个