JavaScript--ES6学习笔记

js笔记

目录

  • js笔记
    • 快速入门
    • 函数
      • 箭头函数–Arrow Function
      • 标准对象
      • Date
      • RegExp
      • JSON
      • 面向对象编程
      • class继承
    • 浏览器
      • 浏览器对象
        • windows
        • navigator
        • screen
        • document
        • history
      • 操作DOM
      • 更新DOM
      • 插入DOM
        • insertBefore
        • 练习
      • 删除DOM
      • 表单操作
      • 操作文件
      • AJAX
        • 安全限制
        • CORS
      • Promise
      • Canvas
    • jQuery
      • 选择器
        • 层级选择器
        • 查找和过滤

快速入门

[快速入门]

  • 动态语言:变量本身不固定,静态语言:变量定义是必须制定变量类型
  • strict模式:再不用var申明变量的情况下,直接使用变量,该变量则为全局变量,在js开头使用’usr strict’;则使用strict模式,强制通过var定义变量,未使用的则报错ReferneceError 。
  • 转义字符 \ 可以转义很多字符,比如 \n 换行,\t 制表符,本身也可以转义 \ ,ASCII字符可以以 \x## 表示十六进制:’\x41’;//等同于’A’,Unicode可以用 ‘\u####‘表示,’\u4e2d\u6587’=‘中文’
  • 多行字符(ES6):....【`:反引号】。
  • 模板字符串(ES6):${XXX}
var name ='小明';
var message = `你好,${name}`;//注意:用的是反引号
alert(message);
  • 字符串的操作:toUpperCase();–变成大写,toLowerCase();–变成小写,indexOf();–会搜索制指定字符串的位置,字符串是不可变的,如果对字符串的某个索引赋值,不会有错,但是也不会变,substring():返回制定区间的子串
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'
  • JavaScript的array可以包含任意的数据类型,并通过索引访问每个元素,要取Array的长度,可以直接访问length属性,给length赋值可以改变Array的大小,indexOf()可以搜索某个指定元素的位置,slice()是Array的截取函数,push()想Array结尾添加若干元素,pop()是删除最后一个元素,unshift()头部添加,shift()删除第一个元素,sort()是排序,reverse()是反转,splice()修改的方法(它可以从指定的索引开始删除若干元素,然后再从该位置添加若干元素),concat()连接并返回另一个新的array,join是把当前Array的每个元素都用指定的字符串连接起来,然后返回连接后的字符串
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(","));
  • 对象:{…} 键值对以xxxx:xxxx形式申明,用’,‘隔开,包含特殊字符则需要用’'括起来,这种变量需要用[‘xxx’]来访问,属性名尽量使用标准的变量名,这样就可以直接通过object.prop的形式访问一个属性了,实际上JavaScript对象的所有属性都是字符串,不过属性对应的值可以是任意数据类型。如果访问一个不存在的属性会返回什么呢?JavaScript规定,访问不存在的属性不报错,而是返回undefined,如果我们要检测xiaoming是否拥有某一属性,可以用in操作符:‘name’ in xiaoming; // true,‘grade’ in xiaoming; // false,不过要小心,如果in判断一个属性存在,这个属性不一定是xiaoming的,它可能是xiaoming继承得到的:‘toString’ in xiaoming; // true,因为toString定义在object对象中,而所有对象最终都会在原型链上指向object,所以xiaoming也拥有toString属性。要判断一个属性是否是xiaoming自身拥有的,而不是继承得到的,可以用hasOwnProperty()方法xiaoming.hasOwnProperty(‘name’); // true,xiaoming.hasOwnProperty(‘toString’); // false
  • if判断建议永远都要写上{},在多个if…else…语句中,如果某个条件成立,则后续就不再继续判断了
  • JavaScript把null、undefined、0、NaN和空字符串’'视为false,其他值一概视为true,因此上述代码条件判断的结果是true。
  • var height = parseFloat(prompt(‘请输入身高(m):’));
  • for循环的一个变体是for … in循环,它可以把一个对象的所有属性依次循环出来:
  • while:for循环在已知循环的初始和结束条件时非常有用。而上述忽略了条件的for循环容易让人看不清循环的逻辑,此时用while循环更佳。while循环只有一个判断条件,条件满足,就不断循环,条件不满足时则退出循环。比如我们要计算100以内所有奇数之和,可以用while循环实现:
  • do … while:最后一种循环是do { … } while()循环,它和while循环的唯一区别在于,不是在每次循环开始的时候判断条件,而是在每次循环完成的时候判断条件:
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循环则可能一次都不执行。
  • JavaScript的默认对象表示方式{}可以视为其他语言中的Map或Dictionary的数据结构,即一组键值对。但是JavaScript的对象有个小问题,就是键必须是字符串。但实际上Number或者其他数据类型作为键也是非常合理的。为了解决这个问题,最新的ES6规范引入了新的数据类型Map。
  • Set和Map类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set中,没有重复的key。要创建一个Set,需要提供一个Array作为输入,或者直接创建一个空Set:重复元素在Set中自动被过滤:注意数字3和字符串’3’是不同的元素。通过add(key)方法可以添加元素到Set中,可以重复添加,但不会有效果:通过delete(key)方法可以删除元素:
'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}
  • 遍历Array可以采用下标循环,遍历Map和Set就无法使用下标。为了统一集合类型,ES6标准引入了新的iterable类型,Array、Map和Set都属于iterable类型。具有iterable类型的集合可以通过新的for … of循环来遍历。for … of循环是ES6引入的新的语法,用for … of循环遍历集合,用法如下:
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);
});

函数

函数

  • js函数默认参数:arguments,只在函数内部起作用,并永远指向函数调用者传入的所有参数,arguments类似于Array但并不是。arguments最常用于判断传入参数的个数
// 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,是一个Array数据,具有iterable属性,可以使用forEach(function(element,index,array){})进行遍历,
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;
}
  • 全局变量:不在任何函数内定义的变量就具有全局作用域。实际上,JavaScript默认有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性,JavaScript实际上只有一个全局作用域。任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续往上查找,最后如果在全局作用域中也没有找到,则报ReferenceError错误,全局变量会绑定到window上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。例如:
// 唯一的全局变量MYAPP:
var MYAPP = {};

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

// 其他函数:
MYAPP.foo = function () {
  return 'foo';
};
  • 局部作用域:为了解决块级作用域,ES6引入了新的关键字let,用let替代var可以申明一个块级作用域的变量
  • 常量:由于var和let申明的是变量,如果要申明一个常量,在ES6之前是不行的,我们通常用全部大写的变量来表示“这是一个常量,不要修改它的值”.ES6标准引入了新的关键字const来定义常量,const与let都具有块级作用域.
'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;
}
  • 解构赋值:从ES6开始,JavaScript引入了解构赋值,可以同时对一组变量进行赋值。var [x, y, z] = [‘hello’, ‘JavaScript’, ‘ES6’];
'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
  • apply:要指定函数的this指向哪个对象,可以用函数本身的apply方法,它接收两个参数,第一个参数就是需要绑定的this变量,第二个参数是Array,表示函数本身的参数。用apply修复getAge()调用:
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
  • 高阶函数:JavaScript的函数其实都指向某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。编写高阶函数,就是让函数的参数能够接收别的函数。
  • MapReduce:它既是一种编程模型,也是一种与之关联的、用于处理和产生大数据集的实现。请查看文献
  • map:map()作为高阶函数,事实上它把运算规则抽象了,因此,我们不但可以计算简单的f(x)=x2,还可以计算任意复杂的函数,比如,把Array的所有数字转为字符串
  • reduce:reduce的用法。Array的reduce()把一个函数作用在这个Array的[x1, x2, x3…]上,这个函数必须接收两个参数,reduce()把结果继续和序列的下一个元素做累积计算,其效果就是:
  • [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;});
  • filter:filter也是一个常用的操作,它用于把Array的某些元素过滤掉,然后返回剩下的元素。false:去掉,true:为不去掉,同时如果不返回则全部去除。练习结果在这里。

箭头函数–Arrow Function

箭头函数

  • 为什么叫Arrow Function?因为它的定义用的就是一个箭头:

x => x * x

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

  • 箭头函数相当于匿名函数,并且简化了函数定义。箭头函数有两种格式,一种像上面的,只包含一个表达式,连{ … }和return都省略掉了。还有一种可以包含多条语句,这时候就不能省略{ … }和return:
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 })

标准对象

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

更细心的同学指出,number对象调用toString()报SyntaxError:

123.toString(); // SyntaxError

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

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的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个字符

  • 第一种方式是直接通过/正则表达式/写出来,第二种方式是通过new RegExp(‘正则表达式’)创建一个RegExp对象。
var re1 = /ABC\-001/;
var re2 = new RegExp('ABC\\-001');

re1; // /ABC\-001/
re2; // /ABC\-001/
  • RegExp对象的test()方法用于测试给定的字符串是否符合条件。
//无法识别连续的空格,用正则表达式试试:
'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']

RegExp

  • 分组:除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用()表示的就是要提取的分组(Group)。比如:^(\d{3})-(\d{3,8})$分别定义了两个组,可以直接从匹配的字符串中提取出区号和本地号码
  • 贪婪匹配:正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。
var re = /^(\d+)(0*)$/;
re.exec('102300'); // ['102300', '102300', '']
  • 全局搜索:JavaScript的正则表达式还有几个特殊的标志,最常用的是g,表示全局匹配,全局匹配可以多次执行exec()方法来搜索一个匹配的字符串。当我们指定g标志后,每次运行exec(),正则表达式本身会更新lastIndex属性,表示上次匹配到的最后索引,全局匹配类似搜索,因此不能使用/^…$/,那样只会最多匹配一次。正则表达式还可以指定i标志,表示忽略大小写,m标志,表示执行多行匹配。
  • 小结:正则表达式非常强大,要在短短的一节里讲完是不可能的。要讲清楚正则的所有内容,可以写一本厚厚的书了。如果你经常遇到正则表达式的问题,你可能需要一本正则表达式的参考书。
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

JSON

  • 是JavaScript Object Notation的缩写,它是一种数据交换格式。道格拉斯·克罗克福特(Douglas Crockford:道格拉斯同学长期担任雅虎的高级架构师,自然钟情于JavaScript。)在2002年的一天发明了JSON这种超轻量级的数据交换格式。JSON实际上是JavaScript的一个子集
    • 在JSON中,一共就这么几种数据类型:
    • number:和JavaScript的number完全一致;
    • boolean:就是JavaScript的true或false;
    • string:就是JavaScript的string;
    • null:就是JavaScript的null;
    • array:就是JavaScript的Array表示方式——[];
    • object:就是JavaScript的{ … }表示方式。
  • JSON还定死了字符集必须是UTF-8,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}

面向对象编程

  • avaScript的面向对象编程和大多数其他语言如Java、C#的面向对象编程都不太一样。如果你熟悉Java或C#,很好,你一定明白面向对象的两个基本概念:
    1. 类:类是对象的类型模板,例如,定义Student类来表示学生,类本身是一种类型,Student表示学生类型,但不表示任何具体的某个学生;
    2. 实例:实例是根据类创建的对象,例如,根据Student类可以创建出xiaoming、xiaohong、xiaojun等多个实例,每个实例表示一个具体的学生,他们全都属于Student类型。
  • 在JavaScript中,这个概念需要改一改。JavaScript不区分类和实例的概念,而是通过原型(prototype)来实现面向对象编程。
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,或者变成任何对象。

  • 请注意,上述代码仅用于演示目的。在编写JavaScript代码时,不要直接用obj.__proto__去改变一个对象的原型,并且,低版本的IE也无法使用_proto_。Object.create()方法可以传入一个原型对象,并创建一个基于该原型的新对象,但是新对象什么属性都没有,因此,我们可以编写一个函数来创建xiaoming:
// 原型对象:
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, 小明!
  • 在JavaScript中,可以用关键字new来调用这个函数,并返回一个对象:

    注意,如果不写new,这就是一个普通函数,它返回undefined。但是,如果写了new,它就变成了一个构造函数,它绑定的this指向新创建的对象,并默认返回this,也就是说,不需要在最后写return this;。

xiaoming ----> Student.prototype ----> Object.prototype ----> null

class继承

  • 新的关键字class从ES6开始正式被引入到JavaScript中。class的目的就是让定义类更简单。
//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这么好用,能不能现在就用上?

现在用还早了点,因为不是所有的主流浏览器都支持ES6class。如果一定要现在就用上,就需要一个工具把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 6~11:国内用得最多的IE浏览器,历来对W3C标准支持差。从IE10开始支持ES6标准;
  • Chrome:Google出品的基于Webkit内核浏览器,内置了非常强悍的JavaScript引擎——V8。由于Chrome一经安装就时刻保持自升级,所以不用管它的版本,最新版早就支持ES6了;
  • Safari:Apple的Mac系统自带的基于Webkit内核的浏览器,从OS X 10.7 Lion自带的6.1版本开始支持ES6,目前最新的OS X 10.11 El Capitan自带的Safari版本是9.x,早已支持ES6
  • Firefox:Mozilla自己研制的Gecko内核和JavaScript引擎OdinMonkey。早期的Firefox按版本发布,后来终于聪明地学习Chrome的做法进行自升级,时刻保持最新;
  • 移动设备上目前iOSAndroid两大阵营分别主要使用AppleSafari和Google的Chrome,由于两者都是Webkit核心,结果HTML5首先在手机上全面普及(桌面绝对是Microsoft拖了后腿),对JavaScript的标准支持也很好,最新版本均支持ES6。

其他浏览器如Opera等由于市场份额太小就被自动忽略了。

另外还要注意识别各种国产浏览器,如某某安全浏览器,某某旋风浏览器,它们只是做了一个壳,其核心调用的是IE,也有号称同时支持IEWebkit的“双核”浏览器。

不同的浏览器对JavaScript支持的差异主要是,有些API的接口不一样,比如AJAX,File接口。对于ES6标准,不同的浏览器对各个特性支持也不一样。

在编写JavaScript的时候,就要充分考虑到浏览器的差异,尽量让同一份JavaScript代码能运行在不同的浏览器中。

浏览器对象

  • JavaScript可以获取浏览器提供的很多对象,并进行操作。

windows

window对象不但充当全局作用域,而且表示浏览器窗口。

window对象有innerWidthinnerHeight属性,可以获取浏览器窗口的内部宽度和高度。内部宽高是指除去菜单栏、工具栏、边框等占位元素后,用于显示网页的净宽高。兼容性:IE<=8不支持。
对应的,还有一个outerWidthouterHeight属性,可以获取浏览器窗口的整个宽高。

navigator

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

  • navigator.appName:浏览器名称;
  • navigator.appVersion:浏览器版本;
  • navigator.language:浏览器设置的语言;
  • navigator.platform:操作系统类型;
  • navigator.userAgent:浏览器设定的User-Agent字符串。
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);
  • 但这样既可能判断不准确,也很难维护代码。正确的方法是充分利用JavaScript对不存在属性返回undefined的特性,直接用短路运算符||计算:var width = window.innerWidth || document.body.clientWidth;

screen

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

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

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

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

documenttitle属性是从HTML文档中的xxx读取的,但是可以动态改变:'use strict';document.title = 'JavaScript';
要查找DOM树的某个节点,需要从document对象开始查找。最常用的查找是根据IDTag 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,设定了httpOnlyCookie将不能被JavaScript读取。这个行为由浏览器实现,主流浏览器均支持httpOnly选项,IEIE6 SP1开始支持。

为了确保安全,服务器端在设置Cookie时,应该始终坚持使用httpOnly

history

history对象保存了浏览器的历史记录,JavaScript可以调用history对象的back()forward (),相当于用户点击了浏览器的“后退”或“前进”按钮。

这个对象属于历史遗留对象,对于现代Web页面来说,由于大量使用AJAX和页面交互,简单粗暴地调用history.back()可能会让用户感到非常愤怒。

新手开始设计Web页面时喜欢在登录页登录成功时调用history.back(),试图回到登录前的页面。这是一种错误的方法。

  • 任何情况,你都不应该使用history这个对象了。

操作DOM

由于HTML文档被浏览器解析后就是一棵DOM树,要改变HTML的结构,就需要通过JavaScript来操作DOM。

始终记住DOM是一个树形结构。操作一个DOM节点实际上就是这么几个操作:

  • 更新:更新该DOM节点的内容,相当于更新了该DOM节点表示的HTML的内容;
  • 遍历:遍历该DOM节点下的子节点,以便进行进一步操作;
  • 添加:在该DOM节点下新增一个子节点,相当于动态增加了一个HTML节点;
  • 删除:将该节点从HTML中删除,相当于删掉了该DOM节点的内容以及它包含的所有子节点。

在操作一个DOM节点前,我们需要通过各种方式先拿到这个DOM节点。最常用的方法是document.getElementById()document.getElementsByTagName(),以及CSS选择器document.getElementsByClassName()

由于IDHTML文档中是唯一的,所以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不支持querySelectorquerySelectorAllIE8仅有限支持。

严格地讲,我们这里的DOM节点是指Element,但是DOM节点实际上是Node,在HTML中,Node包括ElementCommentCDATA_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('测试通过!'); }

更新DOM

可以直接修改节点的文本,方法有两种:

一种是修改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被自动编码,无法设置一个

你可能感兴趣的:(study)