Javascript基本认识

资料来源

入门

立即调用的函数表达式

IIFE = Immediately-Invoked Function Expression

可避免污染全局变量。

数据类型

数组中空位(hole)

length会算上空位。

foreach,for…in, Object.keys方法遍历时会跳过空位。

运算符

指数 运算符是**,是右结合的运算符。

2**4= 16 amazing

2** 3** 2 =512 === 2** (3**2 )

对象object的转化机制

除了data对象这种特殊的对象,会先执行toString方法,其他一律先执行valueOf 方法,然后在执行toString。

在执行比较时object会先进行转化

===严格相等运算在比较复合类型值时,比较的是地址。

undefinednull 进行对比

undefined == null 结果为true,与其他对比时均为false

运算符

布尔运算

执行短路策略,如果第一个数判断不能确认最终布尔运算的结果,那么就返回符合后面的值(是值,而不是布尔值)

0 || 8888 || 9999

第一个数为0不能判断,那么计算机这一步返回 8888 || 9999 , 这一步ok,返回true。

0 || false|| “” ==> 返回 “”

text = text || “” 或运算可以用来设置默认值。

位运算

一个数与自身的取反值相加,等于-1。因为负数是用补码表示的:

补码=反码+1。

那么一个数自身的相反与之相加,就一定是全1,全1 是一个负数,就是一个补码,这个补码对应的原码=~(补码-1)=1,

补码=-原码,因此也就是说全1对应的二进制数就是-1。上诉结论没毛病。

~~2.9 等于 2 ,双取反相当于取整,舍弃小数部分。

语法专题

null转化为数值时为0,undefined转为数值时为NaN。

null+1=1 ; undefined + 1 = NaN

标准库

属性描述对象

obj = {p:‘jewinh’}

p,是对象的属性,同时,他也是一个对象,也叫作属性描述对象:

{value: 123,writable: false,//值是否可写enumerable: true,//是否可被遍历configurable: false,//定义这个属性的属性描述对象是否可配置get: undefined,//set: undefined
}

enumerable 可以设置隐藏属性。以下3种情况不会把隐藏属性搞出来。

  • for … in …循环
  • Object.keys方法
  • JSON.stringify方法

configurable 与 writable 只要有一个为true,value就可以被改写。configurable 为false 时,writeable可置为false,不能置为true。

属性描述对象可以被控制为不能拓展。preventExtension

seal(密封,海豹) ,he sealed the envelope and put on a stamp.

Object.seal 方法, 相当于configurable : false 。 锁住谁也不许走。

freeze,连值也不让改,相当于seal 之后,writable:false

Array对象

Array

Array.isArray(arr) 可以判断变量arr是否为数组,而typeof arr 返回的是object

push ,pop , shift ,unshift , 在数组头或尾添加或者删除一个元素。

join (把数组中所有的元素,用某个特定的字符串链接起来。)

concat , 合并数组,返回数组对象的引用,属于浅拷贝,如果

reverse 颠倒 , slice 切片,[slais],可以把类数组对象转化为数组

Array.prototype.slice.call({ 0 : ‘a’ , 1 : ‘b’ , length : 2 })

splice : he taught me to edit and splice film 剪接。

arr.splice(start splice , how long do you want to splice)

sort

[10111, 1101, 111].sort(function (a, b) {return a - b;
})
// [111, 1101, 10111]
//从小到大排列
//sort后面跟的函数的运行原理,如果a-b小于0,则a一定在b之前。

map

arr.map(func(elem , index ,arr )) , 可以接受元素,索引,数组本身,这三个参数。

注:map会跳过空位(empty)

1.不修改arr本身

2.执行原理:相当于把arr中所有的值当做输入参数,给func,把返回值对应的放在一个新的数组。

arr.map(func(e,i,a),newArr),newArr 绑定func中的this变量。

foreach

与map相似,但是不会有返回值,map返回一个操作后的新的数组。foreach 无法break,会跳过空位

fliter使用方法与map类似

把过滤原则写到函数中即可,某元素运算完成后,return true的元素留下来。

some(func),every(func)

some ,有一个元素使得func返回true,就返回true

every,所有元素使得func都返回true,才返回true。

特别的,空数组,[],some返回false,every返回true。

reduce, reduceRight

reduce 从左往右,+个right,就变成从右往左。

传入的函数arr.reduce( func(累计值,当前值) , 初始值 )

每次运行函数return的值都会存在累计值中。

  • 初始值有时,初始值给累计值,当前值为数组第一个元素
  • 初始值没时,累计值等于第一个元素,当前值等于第二个元素。

indexOf() , lastIndexOf()

索引,找到第一个出现或者最后一个出现这个元素的

NaN无法被识别,因为内部使用的是“===”,因为NaN === NaN 会返回false。

包装对象

String,Number,Boolean 三个函数。

  • +new , var str = new String(‘abc’) ,str 就是一个对象
  • 不加new时,String函数就是可以把其他数据类型转化为字符串。

用方法valueOf可以让上诉三个函数生成的对象变回原来的值。

基本类型转换为对象类型时,这个对象是暂时的,调用完毕后该对象自动销毁,下一次调用时,他又重新生成新的对象。

如果需要添加,则需要使用他的原型对象,prototype。

对象进行逻辑运算时,自动转化为true

Boolean

  • +new,返回的是对象,在if中时,就一定判为true
  • 不加new ,就等效于双重否定:!!

Number

  • Number有一些静态属性,类似于宏定义,就是定义了正无穷,负无穷等一系列的值。
  • (数字).toString(进制数),直接toString会默认转化为10进制。
  • 可以支持自定义方法,prototype

String

  • 定义在对象本身,不是定义在对象实例中的方法,称之为静态方法。例如:String.fromCharCode(104,101,108,108,110)–>hello

trim(),用来去除字符串两端的 换行,制表,回车,空格符。

substring,substr , slice , indexOf , match , search , replace , split ,

Math

  • 静态属性:有一些特殊值可以直接调用。
  • 静态方法: abs , ceil(天花板的意思) , floor(底板的意思) , random ,round
  • 三角函数:sin ,cos ,tan ,asin ,acos ,atan

面向对象

new

使用new+构造函数,会新建一个空对象,构造函数中的this,就指向这个新的空对象。同new会返回这个新建的对象

this

应避免循环套用,可使用that把this指向的东西给that,确保指针是你所期望指向的地方。

call

func.call(thisvalue, arg1,arg2)

函数的call方法,就是可以指定函数内部this要指定的对象,以及需要输入的参数。

在call中使用this,那么this就指向func,后面跟参数

当对象的原生方法被继承使用后,正常调用将无法使用原来的函数,这是可以采用call方法实现调用:
Object.prototype.hasOwnProperty.call(obj, 'toString')
hasOwnProperty是对象数据类型的原型函数,.call方法就可以让强行让obj成为Object中的this,而这个hasOwnProperty就是没修改过的,原版函数,这样调用就不受继承后覆盖影响啦。

apply

与call类似,都是改变this的指向,不过apply第二个参数是传入数组。这个机制可以做出一些事情:

  //数组求最大值var a = [10, 2, 4, 15, 9];Math.max.apply(null, a) // 15//数组空元素转化为undefineArray.apply(null, ['a', ,'b'])
// [ 'a', undefined, 'b' ]//对象转化为数组, apply参数为对象,返回的为数组
Array.prototype.slice.apply({0: 1, length: 1}) // [1]
Array.prototype.slice.apply({0: 1}) // []
Array.prototype.slice.apply({0: 1, length: 2}) // [1, undefined]
Array.prototype.slice.apply({length: 1}) // [undefined]

bind

当新建一个指针时,若赋值的函数中的含有this,那么就容易出现指向错误的问题,那么就可以用bind,把函数中的this,绑定给一个你想让他绑定的对象,这样就可以避免错误。

除了可以绑定函数外,还可以绑定参数。

element.addEventListener('click', o.m.bind(o));
//bind生成的匿名函数没有东西存,后面如果想取消绑定,将无法取消。
//当下次点击的时候,就不能重新绑定,就导致下面函数失效。
element.removeEventListener('click', o.m.bind(o));
//正确的做法是用一个变量存起来。
var listener = o.m.bind(o);
element.addEventListener('click', listener);
//  ...
element.removeEventListener('click', listener);

bind 方法结合 call 方法使用:

call方法第一个参数是一个对象,

[1, 2, 3].slice(0, 1) // [1]
// 等同于
Array.prototype.slice.call([1, 2, 3], 0, 1) // [1]
//改写成下面这样:
var slice = Function.prototype.call.bind(Array.prototype.slice);
slice([1, 2, 3], 0, 1) // [1]
//这段的意思是,Function有一个call函数.
//现在将Function绑定Array.prototype.slice.
//新的slice函数指向这个call函数
/*
var Function={...prototype:{call:function(){this...//call函数的操作。这里的this也是call的第一个参数//当使用.bind(Array.prototype.slice)方法时,//这里的this本来是指向Function.prototype的//现在把这里的this绑定到Array.prototype.slice//所以上下两者完全等价。}}
}
*/

对象的继承

prototype

当构造对象时,构造函数中的prototype属性会自动被实例对象继承,相当于实例对象中有一组指针指向构造函数中的prototype属性。

访问的顺序是,先访问实例对象定义的属性,如果实例中有,读实例中的,如果实例中没有,则读原型对象中的。

原型链

原型是一路继承过来的,所有函数都可以被当做构造函数,最后的尽头是null,Object.prototype的对象原型是null。这就是尽头。

constructor()

这是一个实例对象指向构造函数的指针,有了这个方法,实例对象就可以调用自身构造函数。

instanceof

实例函数 instanceof 构造函数 检查的是整个原型链,就是实例函数的原型链中,是否包含构造函数

构造函数.prototype.isPrototypeOf(实例函数)检查的是

构造函数继承

function Sub(value) {Super.call(this);//把Sub的父类Super中的this指向Sub。相当于把父类的属性整个搬过来。this.prop = value;
}
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;

多重继承小技巧

function M1() {this.hello = 'hello';
}function M2() {this.world = 'world';
}function S() {M1.call(this);M2.call(this);
}// 继承 M1
S.prototype = Object.create(M1.prototype);
// 继承链上加入 M2
Object.assign(S.prototype, M2.prototype);// 指定构造函数
S.prototype.constructor = S;var s = new S();
s.hello // 'hello'
s.world // 'world'

异步操作概述

主线程会去执行同步任务,同步任务完成后,看异步任务是否已经满足执行条件,满足就进入主线程中执行,这时就变成同步任务了,然后一个个执行异步任务,当任务队列清空,程序就运行结束。

异步任务的写法通常是采用回调函数,一旦异步任务重新进入主线程,就会执行相应的回调函数,如果没有回调函数,就不会进入任务队列。

Event Loop ,事件循环,事件循环机制不断在扫码异步任务是否已完成,并判断是否进入主线程。

异步操作模式

回调函数

function f1(callback) {// ...callback();
}function f2() {// ...
}f1(f2);
//显然,当函数f1执行完之后,才会调用f2。
//这种写法的缺陷很明显,f1函数里面有一句调用f2,会导致代码的耦合程度很高,不利于维护。

事件监听

f1.on('done', f2);function f1() {setTimeout(function () {// ...f1.trigger('done');}, 1000);
}
//这种做法有点像Linux中的信号灯,通过trigger给引擎说,我这边已经完成,可以执行我绑定的函数了。这种去耦合有利于实现模块化。但也使得代码阅读时比较难看出谁是主流程。

发布/订阅

publish - subscribe pattern 发布/订阅模式,也可以叫做观察者模式observe pattern

//这个模式有多种实现,下面采用的是 Ben Alman 的 Tiny Pub/Sub,这是 jQuery 的一个插件。
jQuery.subscribe('done', f2);
function f1() {setTimeout(function () {// ...jQuery.publish('done');}, 1000);
}
jQuery.unsubscribe('done', f2);
//这种做法的好处是可以监控程序的运行。可以通过查看“消息中心”,了解有多少信号,每个信号有多少订阅者。

异步操作流程控制

串行执行:

function final(value) {console.log('完成: ', value);
}async(1, function (value) {async(2, function (value) {async(3, function (value) {async(4, function (value) {async(5, function (value) {async(6, final);});});});});
});
// 参数为 1 , 1秒后返回结果
// 参数为 2 , 1秒后返回结果
// 参数为 3 , 1秒后返回结果
// 参数为 4 , 1秒后返回结果
// 参数为 5 , 1秒后返回结果
// 参数为 6 , 1秒后返回结果
// 完成:  12

上面代码中,六个回调函数的嵌套,不仅写起来麻烦,容易出错,而且难以维护。

串行执行

我们可以编写一个流程控制函数,让它来控制异步任务,一个任务完成以后,再执行另一个。这就叫串行执行。

var items = [ 1, 2, 3, 4, 5, 6 ];
var results = [];function async(arg, callback) {console.log('参数为 ' + arg +' , 1秒后返回结果');setTimeout(function () { callback(arg * 2); }, 1000);
}function final(value) {console.log('完成: ', value);
}function series(item) {if(item) {async( item, function(result) {results.push(result);return series(items.shift());});} else {return final(results[results.length - 1]);}
}series(items.shift());

并行执行:

var items = [ 1, 2, 3, 4, 5, 6 ];
var results = [];function async(arg, callback) {console.log('参数为 ' + arg +' , 1秒后返回结果');setTimeout(function () { callback(arg * 2); }, 1000);
}function final(value) {console.log('完成: ', value);
}items.forEach(function(item) {async(item, function(result){results.push(result);if(results.length === items.length) {final(results[results.length - 1]);}})
});

并行与串行结合:

原因是,如果单用串行,太慢,单用并行运行速度快,但会占用大量系统资源,因此两者结合使用,可以调节到一个想要的速度。

var items = [ 1, 2, 3, 4, 5, 6 ];
var results = [];
var running = 0;
var limit = 2;function async(arg, callback) {console.log('参数为 ' + arg +' , 1秒后返回结果');setTimeout(function () { callback(arg * 2); }, 1000);
}function final(value) {console.log('完成: ', value);
}function launcher() {while(running < limit && items.length > 0) {var item = items.shift();async(item, function(result) {results.push(result);running--;if(items.length > 0) {launcher();} else if(running == 0) {final(results);}});running++;}
}launcher();

定时器

setTimeout()
var timerId = setTimeout(func|code, delay);//最后两个参数将在1000ms之后,作为回调函数的参数,赋值给回调函数。
setTimeout(function (a,b) {console.log(a + b);
}, 1000, 1, 1);//对于对象方法,如果直接使用会使得对象中直接指向this。
//可以通过bind或者放入一个函数中避免。
var x = 1;var obj = {x: 2,y: function () {console.log(this.x);}
};
//直接引用
setTimeout(obj.y, 1000) // 1//放入一个函数中
setTimeout(function () {obj.y();
}, 1000);
// 2//使用bind绑定一下。
setTimeout(obj.y.bind(obj), 1000) //2
setInterval()

interval 间隔的意思,每隔一段时间执行一下,是个死循环

//每过1000ms就执行一次。
var i = 1
var timer = setInterval(function() {console.log(2);
}, 1000)
//setInterval 有一个问题,它是每次开始就会开始计时,而不是每次结束后,这样就会导致如果执行太慢,两次执行的时间就很短,很不确定,因此可以使用setTimeout代替。
var i = 1;
var timer = setTimeout(function f() {// ...timer = setTimeout(f, 2000);
}, 2000);
clearTimeout & clearInterval

setTimeout & setInterval 两个函数执行后会返回计数器编号,将该编号传给clearTimeout & clearInterval 可以取消对应的定时器。

var id1 = setTimeout(f, 1000);
var id2 = setInterval(f, 1000);clearTimeout(id1);
clearInterval(id2);
debounce(防抖动)

防抖动设计代码:

$('textarea').on('keydown', debounce(ajaxAction, 2500));function debounce(fn, delay){var timer = null; // 声明计时器return function() {var context = this;var args = arguments;clearTimeout(timer);timer = setTimeout(function () {fn.apply(context, args);}, delay);};
}
运行机制
//setTimeout , setInterval 两个函数,如果后面跟了一个需要运行很久的任务(超过100ms),那么就相当于阻塞了。到了100ms之后,会等待,直到verylongTask执行完毕后,someTask会立刻执行。
setTimeout(someTask, 100);
veryLongTask();//如果设置的延时为0,并不会立刻执行,因为setTimeout是异步任务,引擎首先执行同步任务。实际上,设置为0并不会是为0,就是说这个延时设置有一个下限,Edge浏览器为4ms,如果电脑使用电池则为16毫秒...
//总的原则是,尽量节约系统资源,因为它是异步任务,对及时性要求不需要太高。
setTimeout(function () {console.log(1);
}, 0);
console.log(2);
// 2
// 1//setTimeout(f, 0)的几个非常重要的用途:
//1.调整事件的发生顺序
//点击之后,先执行A,但是A里面是一个异步任务,因此会等到下一轮事件循环执行。此时C应先于B执行。// HTML 代码如下
// <input type="button" id="myButton" value="click">var input = document.getElementById('myButton');input.onclick = function A() {setTimeout(function B() {input.value +=' input';}, 0)
};document.body.onclick = function C() {input.value += ' body'
};//2.延后某些任务的触发
// HTML 代码如下
// <input type="text" id="input-box">document.getElementById('input-box').onkeypress = function (event) {this.value = this.value.toUpperCase();
}
//用户输入字符时,只会把当前输入字符之前的字符转化为大写,因为最后一个输入的字符没有发送给浏览器,浏览器不知道。所以应该延迟一点点,等输入之后,‘过一会’ 再发过去,就ok
//改写成下面这样就可以,每次输入完了,才执行,起到“等一等”的作用。
document.getElementById('input-box').onkeypress = function() {var self = this;setTimeout(function() {self.value = self.value.toUpperCase();}, 0);
}
//3.避免DOM造作堆积,JavaScript 执行速度远高于 DOMvar div = document.getElementsByTagName('div')[0];// 写法一
for (var i = 0xA00000; i < 0xFFFFFF; i++) {div.style.backgroundColor = '#' + i.toString(16);
}// 写法二
var timer;
var i=0x100000;
function func() {timer = setTimeout(func, 0);div.style.backgroundColor = '#' + i.toString(16);if (i++ == 0xFFFFFF) clearTimeout(timer);
}
timer = setTimeout(func, 0);//写法一会造成浏览器“堵塞”,写法二则避免了这一点。这就是setTimeout(f, 0)的好处。

promise对象

没有promise时,异步任务会横向发展而不是向下发展,就会变得很混乱。promise对象解决了这一问题,

promise有三种状态,未完成(pending), 成功(fulfilled),失败(rejected)

执行的结果只有两种:

  • 异步操作成功,Promise 实例传回一个值(value),状态变为fulfilled
  • 异步操作失败,Promise 实例抛出一个错误(error),状态变为rejected

JS提供原生的Promise构造函数,用来生成Promise实例:

var promise = new Promise(function (resolve, reject) {// ...if (/* 异步操作成功 */){resolve(value);} else { /* 异步操作失败 */reject(new Error());}
});
//上面代码中,Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己实现。//resolve函数的作用是,将Promise实例的状态从“未完成”变为“成功”(即从pending变为fulfilled),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。reject函数的作用是,将Promise实例的状态从“未完成”变为“失败”(即从pending变为rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。//下面是一个例子。function timeout(ms) {return new Promise((resolve, reject) => {setTimeout(resolve, ms, 'done');});
}timeout(100)//then方法使用,接收两个回调函数,第一个为成功时的操作,第二个为失败时的操作。
var p1 = new Promise(function (resolve, reject) {resolve('成功');
});
p1.then(console.log, console.error);
// "成功"var p2 = new Promise(function (resolve, reject) {reject(new Error('失败'));
});
p2.then(console.log, console.error);
// Error: 失败//then方法链式使用
p1.then(step1).then(step2).then(step3).then(console.log,console.error);
//原则是错误会传递,成功不会往下传。

微任务

//then属于微任务,是本轮事件循环执行的,而setTimeout是下一轮事件开始时执行。
//执行顺序:同步任务>微任务(then)>setTimeout
setTimeout(function() {console.log(1);
}, 0);new Promise(function (resolve, reject) {resolve(2);
}).then(console.log);console.log(3);
// 3
// 2
// 1

DOM

DOM的最小组成单位为节点node,

节点的类型有七种。

  • Document:整个文档树的顶层节点
  • DocumentTypedoctype标签(比如<!DOCTYPE html>
  • Element:网页的各种HTML标签(比如<body><a>等)
  • Attribute:网页元素的属性(比如class="right"
  • Text:标签之间或标签包含的文本
  • Comment:注释
  • DocumentFragment:文档的片段

节点树

浏览器提供document节点,代表整个文档。

文档的第一层只有一个节点<html> ,这个节点是根节点root node,除根节点外,其他节点都有三种层级关系。

  • 父节点关系(parentNode):直接的那个上级节点
  • 子节点关系(childNodes):直接的下级节点
  • 同级节点关系(sibling):拥有同一个父节点的节点

DOM提供操作接口用来获取三种关系的节点。

子节点接口:firstChild , lastChild 等属性

同级节点:nextSibling , previousSibling

node接口

属性

nodeType

返回一个整数值,表示节点类型

document.nodeType // 9

不同节点的nodeType属性值和对应的常量如下。

  • 文档节点(document):9,对应常量Node.DOCUMENT_NODE
  • 元素节点(element):1,对应常量Node.ELEMENT_NODE
  • 属性节点(attr):2,对应常量Node.ATTRIBUTE_NODE
  • 文本节点(text):3,对应常量Node.TEXT_NODE
  • 文档片断节点(DocumentFragment):11,对应常量Node.DOCUMENT_FRAGMENT_NODE
  • 文档类型节点(DocumentType):10,对应常量Node.DOCUMENT_TYPE_NODE
  • 注释节点(Comment):8,对应常量Node.COMMENT_NODE

确定节点类型时,使用nodeType属性是常用方法。

var node = document.documentElement.firstChild;
if (node.nodeType === Node.ELEMENT_NODE) {console.log('该节点是元素节点');
}
nodeName

属性返回节点的名称。

// HTML 代码如下
// <div id="d1">hello world</div>
var div = document.getElementById('d1');
div.nodeName // "DIV"

上面代码中,元素节点<div>nodeName属性就是大写的标签名DIV

不同节点的nodeName属性值如下。

  • 文档节点(document):#document
  • 元素节点(element):大写的标签名
  • 属性节点(attr):属性的名称
  • 文本节点(text):#text
  • 文档片断节点(DocumentFragment):#document-fragment
  • 文档类型节点(DocumentType):文档的类型
  • 注释节点(Comment):#comment
nodeValue

属性返回一个字符串,表示当前节点本身的文本值,该属性可读写。

只有文本节点(text)、注释节点(comment)和属性节点(attr)有文本值,因此这三类节点的nodeValue可以返回结果,其他类型的节点一律返回null。同样的,也只有这三类节点可以设置nodeValue属性的值,其他类型的节点设置无效。

// HTML 代码如下
// <div id="d1">hello world</div>
var div = document.getElementById('d1');
div.nodeValue // null
div.firstChild.nodeValue // "hello world"

上面代码中,div是元素节点,nodeValue属性返回nulldiv.firstChild是文本节点,所以可以返回文本值。

testContent

返回当前节点和它的所有后代节点的文本内容。

// HTML 代码为
// <div id="divA">This is <span>some</span> text</div>document.getElementById('divA').textContent
// This is some text

…还有其他属性,不一一列举。

方法

var p = document.createElement('p');
document.body.appendChild(p);
//上面代码新建一个<p>节点,将其插入document.body的尾部。

节点树跟文件目录很像,它定义了一些方法,无非就是对这些节点增删改查,没有什么特别的。非常像文件io。

mutation observer API(变动观察者)

DOM发生变动,就会触发这个API。可以类比成操作系统监听文件系统发生的事情。

var article = document.querySelector('article');var  options = {'childList': true,'attributes':true
} ;observer.observe(article, options);
  • childList:子节点的变动(指新增,删除或者更改)。
  • attributes:属性的变动。
  • characterData:节点内容或节点文本的变动。

mutation observer 处理的就是一个个 MutationRecord 实例所组成的数组。

MutationRecord对象包含了DOM的相关信息,有如下属性:

  • type:观察的变动类型(attributescharacterData或者childList)。
  • target:发生变动的DOM节点。
  • addedNodes:新增的DOM节点。
  • removedNodes:删除的DOM节点。
  • previousSibling:前一个同级节点,如果没有则返回null
  • nextSibling:下一个同级节点,如果没有则返回null
  • attributeName:发生变动的属性。如果设置了attributeFilter,则只返回预先指定的属性。
  • oldValue:变动前的值。这个属性只对attributecharacterData变动有效,如果发生childList变动,则返回null

有了这一章节的内容,我们就好像拥有了操作系统一样,可以合理的管理好网页文本资源,非常的棒。因此初学者应该学DOM。

事件

EventTarget

DOM的时间操作(监听+触发)都是定义在EventTarget这个借口中。所有节点对象都部署了这个接口,其他一些需要事件通信的浏览器内置对象(XMLhttpRequest , AudioNode , AudioContext )也部署了这个接口。

EventTarget 主要提供了三个实例方法:

addEventListener :绑定事件的监听函数

removeEventListener:移除事件的监听函数

dispatchEvent:触发事件 (dispatch 调度的意思)

addEventListener

//范式
target.addEventListener(type, listener[, useCapture]);//例子
function hello() {console.log('Hello world');
}var button = document.getElementById('btn');
button.addEventListener('click', hello, false);

该方法接受三个参数。

  • type:事件名称,大小写敏感。
  • listener:监听函数。事件发生时,会调用该监听函数。
  • useCapture:布尔值,表示监听函数是否在捕获阶段(capture)触发(参见后文《事件的传播》部分),默认为false(监听函数只在冒泡阶段被触发)。该参数可选。

关于这些参数更详细的论述,用到再查。

removeEventListener

匿名函数无法移除,输入参数与addEventListener不同无法移除。

dispatchEvent

//这个函数是判断一件事是否被调度?没太理解,先往后看。相当于信号灯之类的东西?
para.addEventListener('click', hello, false);
var event = new Event('click');
para.dispatchEvent(event);//dispatchEvent函数可以判断对象是否被取消。
var canceled = !cb.dispatchEvent(event);
if (canceled) {console.log('事件取消');
} else {console.log('事件未取消');
}

事件模型

监听函数listener,这是事件驱动编程模式的主要编程方法

绑定监听函数

javascript有三种方法,可以为事件绑定监听函数

  • HTML的on - 属性

    <!--
    直接在html中定义某些事件的监听代码。
    -->
    <body onload="doSomething()">
    <div onclick="console.log('触发事件')"><!--
    这种方法的事件,是把on-xx后面写的事件,载入到js引擎中执行,因此要加(),执行时,是冒泡式执行,先执行子元素,再执行父元素。
    当然,因为是载入到js引擎中执行,我们也可以直接用js来替代这种做法,只是用DOM去定位的时候稍微麻烦一些。
    -->
    <div onClick="console.log(2)"><button onClick="console.log(1)">点击</button>
    </div>el.setAttribute('onclick', 'doSomething()');
    // 等同于
    // <Element onclick="doSomething()">
  • 元素节点的事件属性

    //元素节点对象有事件属性,同样可以指定监听函数,这里不用加(),同样的,也只在冒泡阶段触发。
    window.onload = doSomething;div.onclick = function (event) {console.log('触发事件');
    };
  • addEventListener()

    DOM节点实例,都有addEventListener方法,

1,2各有缺点,都不推荐使用,主推3。原因:

都有这个接口,可以绑定多个,可以指定什么阶段触发。

this的指向

可以认为绑定监听事件,就是bind了一下,这时this必定指向绑定的对象。

事件的传播propagation

什么是冒泡阶段,什么是捕获阶段???这里给出解释

一个事件被触发后,会在子元素和父元素之间传播,传播分三个阶段:

  • 第一阶段,从window对象传导到目标节点(上层传到底层),称为"捕获阶段capture phase"。

    类比思考:相当于我要打开一个文件,要找到这个文件的路径,捕获到路径了,才能打开,行业术语,也就是捕获。正在找

  • 第二阶段,在目标节点上触发,称为"目标阶段target phase"

    类比思考:找到。

  • 第三阶段,从目标节点传导回windows对象(从底层传递回上层),称为"冒泡阶段bubbling phase"

举个例子:

<div><p>点击</p>
</div>var phases = {1: 'capture',2: 'target',3: 'bubble'
};var div = document.querySelector('div');
var p = document.querySelector('p');div.addEventListener('click', callback, true);
p.addEventListener('click', callback, true);
div.addEventListener('click', callback, false);
p.addEventListener('click', callback, false);function callback(event) {var tag = event.currentTarget.tagName;var phase = phases[event.eventPhase];console.log("Tag: '" + tag + "'. EventPhase: '" + phase + "'");
}// 点击以后的结果
// Tag: 'DIV'. EventPhase: 'capture'
// Tag: 'P'. EventPhase: 'target'
// Tag: 'P'. EventPhase: 'target'
// Tag: 'DIV'. EventPhase: 'bubble'

注意,浏览器总是假定click事件的目标节点,就是点击位置嵌套最深的那个节点(本例是<div>节点里面的<p>节点)。所以,<p>节点的捕获阶段和冒泡阶段,都会显示为target阶段。

事件传播的最上层对象是window,接着依次是documenthtmldocument.documentElement)和bodydocument.body)。也就是说,上例的事件传播顺序,在捕获阶段依次为windowdocumenthtmlbodydivp,在冒泡阶段依次为pdivbodyhtmldocumentwindow

事件代理

把子节点的监听函数定义在父节点上,由父节点统一监听处理多个子元素的事件,叫做事件代理(delegation)。

显然:事件代理的触发只能在冒泡阶段,因为要确认父子关系,在capture phase 是不可以的。

典型的就是处理列表啦:

var ul = document.querySelector('ul');ul.addEventListener('click', function (event) {if (event.target.tagName.toLowerCase() === 'li') {// some code}
});

有时,我们也希望在某个事件发生后,不再往下传播,我们可以使用stopPropagation。但是stopPropagation不会影响click事件的客观存在,只是停止了传播,如果click事件还绑定了其他的事件监听器,则会正常执行。如果想其他也不执行,应该用stopImmediatePropagation,相当于直接取消了click事件。

// 事件传播到 p 元素后,就不再向下传播了
p.addEventListener('click', function (event) {event.stopPropagation();
}, true);// 事件冒泡到 p 元素后,就不再向上冒泡了
p.addEventListener('click', function (event) {event.stopPropagation();
}, false);//stopPropagation的应用。
p.addEventListener('click', function (event) {event.stopPropagation();console.log(1);
});p.addEventListener('click', function(event) {// 会触发console.log(2);
});//stopImmediatePropagation的应用
p.addEventListener('click', function (event) {event.stopImmediatePropagation();console.log(1);
});p.addEventListener('click', function(event) {// 不会被触发console.log(2);
});

Event对象

事件和监听函数直接是怎么通讯的?通过Event对象,

event = new Event(type, options);//options是给对象进行配置用的
//一般主要有bubbles,cancelable两个
//bubbles为true,就是事件冒泡(默认为false,事件默认不冒泡)。var ev = new Event('look',{'bubbles': true,'cancelable': false}
);
//dispatchEvent函数可以触发这个定义好的事件look。
document.dispatchEvent(ev);// HTML 代码为
// <div><p>Hello</p></div>
var div = document.querySelector('div');
var p = document.querySelector('p');function callback(event) {var tag = event.currentTarget.tagName;console.log('Tag: ' + tag); // 没有任何输出
}//这里定义了div的监听函数,最后的参数为false,说明在冒泡时监听,而click事件,不会冒泡。
//因此当p触发click的时候,事件向下传播,一直到叶,然后停止。不会冒泡,给到div,因此定义在div上click监听函数,不会被触发。
div.addEventListener('click', callback, false);var click = new Event('click');
p.dispatchEvent(click);//如果这个事件是在div元素上触发。这个事件都会触发。因为这个跟传播没有任何卵关系,点击即触发,当然,是不会送屠龙宝剑的。
div.dispatchEvent(click);

Event实例属性,bubbles,eventPhase

Event.bubbles 属性返回一个布尔值,新建event时写进去的,读一下而已

eventPhase 返回 ,0:无事发生,1:捕获阶段,2:目标节点event.target指向的节点 , 3:冒泡阶段

cancelable , cancelBubble , defaultPrevented

cancelable 默认false , 就是说一个事件发生了,Event.preventDefault()方法就不会起作用。最好用之前判断一下。

其实这种做法也相当于执行Event.stopPropagation()

function preventEvent(event) {if (event.cancelable) {event.preventDefault();} else {console.warn('This event couldn\'t be canceled.');console.dir(event);}
}
//defaultPrevented属性是表示该事件是否被调用过。
if (event.defaultPrevented) {console.log('该事件已经取消了');
}
//没太想到使用场景,见到实际例子再回来看。
currentTarget , target

currentTarget 返回的是当前被触发的监听函数所绑定的节点。

target 返回的是目标节点。

比较绕,看一个例子。

// HTML代码为
// <p id="para">Hello <em>World</em></p>
function hide(e) {console.log(this === e.currentTarget);  // 总是 trueconsole.log(this === e.target);  // 有可能不是 truee.target.style.visibility = 'hidden';
}para.addEventListener('click', hide, false);
//如果点的是hello,target指向的是P节点,currentTarget指向的也是P节点。
//如果点的是world,target指向的是p的子节点<em>,currentTarget指向的还是p节点。因为currentTarget是触发函数的时候才确定的,target是点击的时候确定的。
event.type
var evt = new Event('foo');
evt.type // "foo"
//click,mousemove等 都是一种事件类型,当然你可以定义任意事件类型。像上面的例子一样。
event.timeStamp

返回一个毫秒时间戳,这个时间是相对于网页加载成功开始算的。

var evt = new Event('foo');
evt.timeStamp // 3683.6999999995896
event.isTrusted

确认事件是否由用户产生为true,脚本产生为false

event.detail

只有浏览器UI事件才具有detail属性,可以返回鼠标点击几次,滚轮滚的距离等信息。

实例方法

上面介绍完属性,这里讲一下方法。

event.preventDefault()

取消浏览器对当前事件的默认行为,但不阻止事件的传播。
如果要阻止可以使用stopPropagation()stopImmediatePropagation()方法。

//下面这个例子就可以保证,当键盘输入非小写字母的时候,就会输入无效。用这个特性可以限制用户一些行为。
// HTML 代码为
// <input type="text" id="my-input" />
var input = document.getElementById('my-input');
input.addEventListener('keypress', checkName, false);function checkName(e) {if (e.charCode < 97 || e.charCode > 122) {e.preventDefault();}
}
event.stopPropagation()

这个方法可以阻止事件的传播

function stopEvent(e) {e.stopPropagation();
}
//这样做之后,el的父节点收不到click事件。
el.addEventListener('click', stopEvent, false);

event.stopImmediatePropagation()

这个方法阻止同一事件的其他监听函数被调用。

function l1(e){e.stopImmediatePropagation();//这里调用了这个方法后,click事件立马消失。//如果只是禁止传播,那么绑定在这个节点上的函数还是能被调用的。但这个就直接消灭了click事件。
}function l2(e){console.log('hello world');
}el.addEventListener('click', l1, false);
el.addEventListener('click', l2, false);

event.composedPath

composed组成的意思。返回一个数组,成员是事件的最底层节点和依次冒泡经过的所有上层节点

// HTML 代码如下
// <div>
//   <p>Hello</p>
// </div>
var div = document.querySelector('div');
var p = document.querySelector('p');div.addEventListener('click', function (e) {console.log(e.composedPath());
}, false);
// [p, div, body, html, document, Window]

由这个函数,我们可以知道,当我们点击时,先往下传播,直到叶节点。例如上面隐藏的例子,点击隐藏,会把该节点下所有子节点都隐藏起来,所以应该是会先向下到底,再向上。只有这样,才能满足父子节点的信息传递,这样的"关系"才是完整的。

各种各样的事件

鼠标,键盘,进度条,表单,触摸,拖拉…用到再看看。

浏览器模型

浏览器内置了JavaScript引擎,并且提供了各种接口,让js可以控制浏览器的各种功能。网页中一旦有js脚本,浏览器加载网页时,就会去执行脚本。

代码嵌入网页

  • script元素中嵌入代码

    <script>var x = 1 + 5;console.log(x);</script>//有一个type属性来指定脚本类型:
    text/javascript,是一个默认值,对于老式浏览器,这个值比较好
    application/javascript,新浏览器建议用这个值。
    <script type="application/javascript">console.log('Hello World');</script>//如果是type属性的值,浏览器不认识,那么就不会执行其中的代码。但是在DOM中依然会存在。
    <script id="mydata" type="x-custom-data">console.log('Hello World');</script>document.getElementById('mydata').text
    //   console.log('Hello World');
  • script元素加载外部脚本

    <script src="/assets/application.js"integrity="sha256-TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs="></script>
    //1.用来src加载之后,<script>里面的js就无效
    //2.src里面的东西必须是纯的js
    //3.integrity(廉正,健全,诚实的意思)属性可以指定外部脚本的hash签名,用来保证脚本的一致性。如果有人修改了脚本浏览器就会拒绝加载。
  • 事件属性

    <button id="myBtn" onclick="console.log(this.id)">点击</button>
    //当事件发生时会直接调用这里的js代码
  • url协议

    url支持javascript:协议,在url位置写入js代码,使用这个url的时候就会执行js代码。

    <a href="javascript:console.log('Hello')">点击</a>
    //如果返回字符串,browser会新建一个文档,展示这个返回的字符串,原有的文档会消失
    //如果返回的不上字符串,那么网页不会跳转,只会执行js代码。可以通过+void或者在后面+void 0 避免跳转。
    <a href="javascript: void new Date().toLocaleTimeString();">点击</a>
    <a href="javascript: new Date().toLocaleTimeString();void 0;">点击</a>

script元素

  1. 浏览器一边下载 HTML 网页,一边开始解析。也就是说,不等到下载完,就开始解析。
  2. 解析过程中,浏览器发现<script>元素,就暂停解析,把网页渲染的控制权转交给 JavaScript 引擎。
  3. 如果<script>元素引用了外部脚本,就下载该脚本再执行,否则就直接执行代码。
  4. JavaScript 引擎执行完毕,控制权交还渲染引擎,恢复往下解析 HTML 网页

注意信息:

  1. 如果部分脚本文件不太重要,应该放到html文件的底部。这样就算在网络不太好的时候,网页的主体部分也能被展示,因为边下载边加载。
  2. 放在

由于边加载边执行的机制,还会出现当你访问一个DOM元素时,它还没加载好,那么遇到这个问题,可以监听DOMContentLoaded事件,这个事件只有在DOM结构生成之后才会触发。

<head><script>document.addEventListener('DOMContentLoaded',function (event) {console.log(document.body.innerHTML);});</script>
</head>//这里提供第二个解决方案,当script标签指定的外部脚本文件下载和解析完成,会触发一个load事件,可以把所需执行的代码,放在这个事件的回调函数里面。
//其实没明白为什么可以...因为你下载的时候不是要等你下载完成吗?那下面的DOM依然没有加载。除非说html先自己去加载了,比外部的js要快。
<script src="jquery.min.js" onload="console.log(document.body.innerHTML)"></script>

浏览器的资源下载:

如果是来自同一个域名的静态资源,浏览器最多同时下载6~20个资源,不同域名就没有这个限制。条件允许,对于大型的项目,可以把静态资源放在不同的域名之下, 可以加快下载速度。

defer属性:

加入defer之后,脚本会延迟执行,等到DOM生成之后,再执行脚本。内置的和动态生成的script不起作用。

defer加载的外部脚本不应该使用document.write方法

async属性

async异步的意思,这个属性是使用另外一个进程去下载脚本,下载时不会阻塞渲染。与defer不同,async是下载完后立刻执行,html要给async的js让路。如果同时有defer和async那么浏览器的行为由async决定。

当然async也不应该使用document.write方法,至于为什么之后再查资料。

脚本动态加载

//动态加载a,b
['a.js', 'b.js'].forEach(function(src) {var script = document.createElement('script');script.src = src;document.head.appendChild(script);
});//有顺序的动态加载a,b。a先加载,b后加载。
['a.js', 'b.js'].forEach(function(src) {var script = document.createElement('script');script.src = src;script.async = false;document.head.appendChild(script);
});//当然还可以指定一些回调函数。
function loadScript(src, done) {var js = document.createElement('script');js.src = src;js.onload = function() {done();};js.onerror = function() {done(new Error('Failed to load script ' + src));};document.head.appendChild(js);
}

加载协议

//默认用http
<script src="example.js"></script>
//指定用https
<script src="https://example.js"></script>
//页面用什么,我们就用什么加载。
<script src="//example.js"></script>

渲染引擎

渲染引擎的作用是,将网页代码渲染为用户视觉可以感知的平面文档。

不同浏览器用不同的引擎:

  • Firefox:Gecko 引擎
  • Safari:WebKit 引擎
  • Chrome:Blink 引擎
  • IE: Trident 引擎
  • Edge: EdgeHTML 引擎

渲染的四个阶段

  1. 解析代码:HTML 代码解析为 DOM,CSS 代码解析为 CSSOM(CSS Object Model)。
  2. 对象合成:将 DOM 和 CSSOM 合成一棵渲染树(render tree)。
  3. 布局:计算出渲染树的布局(layout)。
  4. 绘制:将渲染树绘制到屏幕。

并不是严格按照顺序执行,有时html还没下载完,浏览器就已经可以显示出内容了。

重流与重绘

渲染树转为网页布局,称为“布局流”(flow),布局显示到页面的过程,称为“绘制”paint。这两个过程都有阻塞效应,而且会耗费很多时间和计算资源。

页面生成后,脚本与样式表操作会触发重流reflow和重绘repaint。

重流一定重绘,重绘不一定重流,改变颜色之后重绘不会重流

由于这个特性,所以尽量不要动高层的DOM,而是去触动底层的DOM,这样系统开销相对小一些。

另外重绘table , flex 布局,开销都会比较大。

浏览器的一个特性是会累积DOM变动,然后一次性执行。

以下是一些优化技巧:

  • 读取 DOM 或者写入 DOM,尽量写在一起,不要混杂。不要读取一个 DOM 节点,然后立刻写入,接着再读取一个 DOM 节点。
  • 缓存 DOM 信息。
  • 不要一项一项地改变样式,而是使用 CSS class 一次性改变样式。
  • 使用documentFragment操作 DOM
  • 动画使用absolute定位或fixed定位,这样可以减少对其他元素的影响。
  • 只在必要时才显示隐藏元素。
  • 使用window.requestAnimationFrame(),因为它可以把代码推迟到下一次重流时执行,而不是立即要求页面重流。
  • 使用虚拟 DOM(virtual DOM)库。
// 重绘代价高
function doubleHeight(element) {var currentHeight = element.clientHeight;element.style.height = (currentHeight * 2) + 'px';
}all_my_elements.forEach(doubleHeight);// 重绘代价低
function doubleHeight(element) {var currentHeight = element.clientHeight;window.requestAnimationFrame(function () {element.style.height = (currentHeight * 2) + 'px';});
}all_my_elements.forEach(doubleHeight);//上面的第一段代码,每读一次 DOM,就写入新的值,会造成不停的重排和重流。第二段代码把所有的写操作,都累积在一起,从而 DOM 代码变动的代价就最小化了。

javascript引擎(虚拟机)

  • Chakra (Microsoft Internet Explorer)
  • Nitro/JavaScript Core (Safari)
  • Carakan (Opera)
  • SpiderMonkey (Firefox)
  • V8 (Chrome, Chromium)

windows对象

指当前浏览器窗口,是当前页面的顶层对象,就是最高一层对象。一个变量如果未声明,那么就是顶层对象的属性

a = 1;
window.a // 1//window.name,这个属性是定义当前浏览器的窗口的名字,在当前窗口跳转,这个对象的属性会保留下来,如果这个窗口关闭了,这个值就消失。可以认为,如果你打开了很多个网页窗口,就会有很多个window对象。
window.name = 'Hello World!';
console.log(window.name)
// "Hello World!"var popup = window.open();
//open方法可以打开一个新的窗口,
if ((popup !== null) && !popup.closed) {// 窗口仍然打开着
}window.open().opener === window // true
//opener属性表达打开当前窗口的父窗口。//这里说明浏览器内部的window其实也是可以互相调用,但是要定义一下,否则没有其他办法可以操作其他窗口。//一般我们建议子窗口和父窗口不要联系比较好。我们可以把opener属性设置为null。
//<a>元素中添加属性rel="noopener"也可以防止新打开的窗口获取父窗口。

frames , length , self , window , frameElement,top , parent

//这两属性只读,其实就是window本身
window.self === window // true
window.window === window // true
//length,frames
frames === window // true
//frames就是window,如果使用frame,iframe。那么window.frames就返回一个数组,也就是说window变成了一个数组,数组中的元素就是各个frame。//window.frameElement属性主要用于当前窗口嵌在另一个网页的情况(嵌入<object>、<iframe>或<embed>元素),返回当前窗口所在的那个元素节点。如果当前窗口是顶层窗口,或者所嵌入的那个网页不是同源的,该属性返回null。// HTML 代码如下
// <iframe src="about.html"></iframe>// 下面的脚本在 about.html 里面,这样就可以指回自己。否则直接用window对象可能就不知道指向哪了。
var frameEl = window.frameElement;
if (frameEl) {frameEl.src = 'other.html';
}//window.top属性指向最顶层窗口,主要用于在框架窗口(frame)里面获取顶层窗口。
//window.parent属性指向父窗口。如果当前窗口没有父窗口,window.parent指向自身。if (window.parent !== window.top) {// 表明当前窗口嵌入不止一层
}

devicePixelRatio

css像素与物理像素的比率

位置大小属性

screenX,screenY , innerHeight , innerWidth , outerHeight, outerWidth , scrollX,scrollY ,pageXOffset , pageYOffset ,isSecureContext

一些方法

alert,prompt,confirm,open ,close ,stop , moveTo , moveBy, resizeTo , resizeBy , scrollTo , scroll , scrollBy , print , focus , blur , getSelection , getComputedStyle ,matchMedia , requestAnimationFrame , cancelAnimationFrame , requestIdleCallback ,

一些事件

load( 发生在文档在浏览器窗口加载完毕时 ),onload属性可以指定这个事件的回调函数。

error( 发生在脚本发生错误时 ) , onerror…

onafterprint , onbeforeprint , onbeforeunload , onhashchange , onlanguagechange , onmessage , onmessageerror , onoffline , ononline , onpagehide , onpageshow , onpopstate , onstorage , onunhandledrejection , onunload .

多窗口操作

iframe元素使得网页可以互相嵌套使用,会形成多窗口,如果子窗口又嵌入其他网页就会形成多级窗口。

top , 顶层窗口

parent , 父窗口

self , 自身

与这些变量对应,浏览器还提供一些特殊窗口名,供window.open()方法,,标签,等引用。

  • _top:顶层窗口
  • _parent:父窗口
  • _blank:新窗口
//下面代码就表示在顶层窗口打开网页。
<a href="somepage.html" target="_top">Link</a>

iframe 嵌入窗口的获取办法

var frame = document.getElementById('theFrame');
var frameWindow = frame.contentWindow;
//上面代码中,frame.contentWindow可以拿到子窗口的window对象。然后,在满足同源限制的情况下,可以读取子窗口内部的属性。// 获取子窗口的标题
frameWindow.document.title
//也可以通过 var frameDocument = frame.contentDocument;
//直接获取document对象。
//<iframe>元素遵守同源政策,只有当父窗口与子窗口在同一个域时,两者之间才可以用脚本通信,否则只有使用window.postMessage方法。

navigator对象的属性

这个对象其实是去识别浏览器到底运行在哪个设备,哪个引擎,有哪些插件…等。

userAgent , plugins , platform , onLine , language , languages , geolocation , cookieEnable , javaEnable() , sendBeacon() ,beacon 烽火

screen对象

属性:height , width , availHeight , availWidth , pixelDepth , colorDepth , orientation ,

cookie

cookie是服务器保持在浏览器的一小段文本信息,每一段cookie大小一般不能超过4KB , 每次浏览器发出请求都会自动附带上这段信息。

cookie主要用来分辨请求是否来自同一个浏览器,以及用来保存一些状态信息,常用场合如下

  • 对话(session)管理:保存登录,购物车等需要记录的信息。
  • 个性化:保存用户偏好,例如网页字体,背景颜色…
  • 追踪:记录和分析用户行为

有些开发者使用 Cookie 作为客户端储存。这样做虽然可行,但是并不推荐,因为 Cookie 的设计目标并不是这个,它的容量很小(4KB),缺乏数据操作接口,而且会影响性能。客户端储存应该使用 Web storage API 和 IndexedDB。

Cookie 包含以下几方面的信息。

  • Cookie 的名字
  • Cookie 的值(真正的数据写在这里面)
  • 到期时间
  • 所属域名(默认是当前域名)
  • 生效的路径(默认是当前网址)

cookie对同一域名的根路径和所有子路径都有效!

cookie由http生成,也由它使用

如果希望浏览器保存cookie,需要在头信息中放置Set-Cookie字段

Set-Cookie:foo=bar//可以生成多个cookieHTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry[page content]//可以添加cookie的属性。
Set-Cookie: <cookie-name>=<cookie-value>; Expires=<date>
Set-Cookie: <cookie-name>=<cookie-value>; Max-Age=<non-zero-digit>
Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>
Set-Cookie: <cookie-name>=<cookie-value>; Path=<path-value>
Set-Cookie: <cookie-name>=<cookie-value>; Secure
Set-Cookie: <cookie-name>=<cookie-value>; HttpOnly//Set-cookie字段里面,可以同时包括多个属性,没有次序的要求
Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly
//例如:
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly//如果服务器想改变一个早先设置的 Cookie,必须同时满足四个条件:Cookie 的key、domain、path和secure都匹配。举例来说,如果原始的 Cookie 是用如下的Set-Cookie设置的。Set-Cookie: key1=value1; domain=example.com; path=/blog
//改变上面这个 Cookie 的值,就必须使用同样的Set-Cookie。Set-Cookie: key1=value2; domain=example.com; path=/blog
//只要有一个属性不同,就会生成一个全新的 Cookie,而不是替换掉原来那个 Cookie。//上面的命令设置了一个全新的同名Cookie,但是属性不同,下次访问网站/blog时,会同时发两个cookie,匹配越精确的,越排在前面
/blog要先发, /后发

http请求:cookie的发送

浏览器发送http请求时,每个请求都会带上相应的cookie,就是把之前服务器发给浏览器的那段信息发会给服务器,这是要使用HTTP头信息的Cookie字段

Cookie: foo=bar//上面代码会发送名为foo的cookie,值为bar,可以包含多个cookieCookie: name=value; name2=value2; name3=value3//例如:
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry//cookie的属性没发?,谁设置的没说?上面的那四个值没有说。

Expries , Max-Age

Max-Age 比 Expries优先 , 不设置这个属性,或者设为null,cookie只在当前session有效,关闭浏览器窗口就会被删除。

Domain , Path

domain , path 两个属性是用来保证同一域名,同一路径下的网页可以用同样的Cookie

Secure , HttpOnly

secure → https , HttpOnly → javascript (设置了,脚本就拿不到了)

document.cookie

document.cookie // "foo=bar;baz=bar"//读cookie
var cookies = document.cookie.split(';');for (var i = 0; i < cookies.length; i++) {console.log(cookies[i]);
}
// foo=bar
// baz=bar//cookie可写
document.cookie = 'fontSize=14';
//写入的时候,Cookie 的值必须写成key=value的形式。注意,等号两边不能有空格。另外,写入 Cookie 的时候,必须对分号、逗号和空格进行转义(它们都不允许作为 Cookie 的值),这可以用encodeURIComponent方法达到。//但是,document.cookie一次只能写入一个 Cookie,而且写入并不是覆盖,而是添加。
document.cookie = 'test1=hello';
document.cookie = 'test2=world';
document.cookie
// test1=hello;test2=world//document.cookie读写行为的差异(一次可以读出全部 Cookie,但是只能写入一个 Cookie),与 HTTP 协议的 Cookie 通信格式有关。浏览器向服务器发送 Cookie 的时候,Cookie字段是使用一行将所有 Cookie 全部发送;服务器向浏览器设置 Cookie 的时候,Set-Cookie字段是一行设置一个 Cookie。//写入 Cookie 的时候,可以一起写入 Cookie 的属性。document.cookie = "foo=bar; expires=Fri, 31 Dec 2020 23:59:59 GMT";

各个属性的写入注意点如下。

  • path属性必须为绝对路径,默认为当前路径。
  • domain属性值必须是当前发送 Cookie 的域名的一部分。比如,当前域名是example.com,就不能将其设为foo.com。该属性默认为当前的一级域名(不含二级域名)。
  • max-age属性的值为秒数。
  • expires属性的值为 UTC 格式,可以使用Date.prototype.toUTCString()进行日期格式转换。
document.cookie = 'fontSize=14; '+ 'expires=' + someDate.toGMTString() + '; '+ 'path=/subdirectory; '+ 'domain=*.example.com';//Cookie 的属性一旦设置完成,就没有办法读取这些属性的值。//删除一个现存 Cookie 的唯一方法,是设置它的expires属性为一个过去的日期。document.cookie = 'fontSize=;expires=Thu, 01-Jan-1970 00:00:01 GMT';

XMLHttpRequest

简介

Asynchronous JavaScript and XML ,

ajax == JavaScript 脚本发起 HTTP 通信的代名词

具体来说,AJAX 包括以下几个步骤。

  1. 创建 XMLHttpRequest 实例
  2. 发出 HTTP 请求
  3. 接收服务器传回的数据
  4. 更新网页数据

如今已经不用xml了,改用json。

这个对象可以发送任何格式的数据,

XMLHttpRequest 是一个构造函数,使用new生成实例

var xhr = new XMLHttpRequest();
//建立好后,即可用来发送请求。
xhr.open('GET', 'http://www.example.com/page.php', true);//指定回调函数。
xhr.onreadystatechange = handleStateChange;function handleStateChange() {// ...
}//AJAX 只能向同源网址(协议、域名、端口都相同)发出 HTTP 请求//完整实例;
var xhr = new XMLHttpRequest();xhr.onreadystatechange = function(){// 通信成功时,状态值为4if (xhr.readyState === 4){if (xhr.status === 200){console.log(xhr.responseText);} else {console.error(xhr.statusText);}}
};xhr.onerror = function (e) {console.error(xhr.statusText);
};xhr.open('GET', '/endpoint', true);
xhr.send(null);

XMLHttpRequest的实例属性

readyState

XMLHttpRequest.readyState返回一个整数,表示实例对象的当前状态。该属性只读。它可能返回以下值。

  • 0,表示 XMLHttpRequest 实例已经生成,但是实例的open()方法还没有被调用。
  • 1,表示open()方法已经调用,但是实例的send()方法还没有调用,仍然可以使用实例的setRequestHeader()方法,设定 HTTP 请求的头信息。
  • 2,表示实例的send()方法已经调用,并且服务器返回的头信息和状态码已经收到。
  • 3,表示正在接收服务器传来的数据体(body 部分)。这时,如果实例的responseType属性等于text或者空字符串,responseText属性就会包含已经收到的部分信息。
  • 4,表示服务器返回的数据已经完全接收,或者本次接收已经失败。

XMLHttpRequest.onreadystatechange属性指向一个监听函数。readystatechange事件发生时(实例的readyState属性变化),就会执行这个属性。

abort()终止请求时,也会造成readyState属性变化。

response

XMLHttpRequest.response属性表示服务器返回的数据体(即 HTTP 回应的 body 部分)。它可能是任何数据类型,比如字符串、对象、二进制对象等等,具体的类型由XMLHttpRequest.responseType属性决定。该属性只读。

如果本次请求没有成功或者数据不完整,该属性等于null。但是,如果responseType属性等于text或空字符串,在请求没有结束之前(readyState等于3的阶段),response属性包含服务器已经返回的部分数据。

responseType

XMLHttpRequest.responseType属性是一个字符串,表示服务器返回数据的类型。这个属性是可写的,可以在调用open()方法之后、调用send()方法之前,设置这个属性的值,告诉服务器返回指定类型的数据。如果responseType设为空字符串,就等同于默认值text

XMLHttpRequest.responseType属性可以等于以下值。

  • “”(空字符串):等同于text,表示服务器返回文本数据。
  • “arraybuffer”:ArrayBuffer 对象,表示服务器返回二进制数组。
  • “blob”:Blob 对象,表示服务器返回二进制对象。
  • “document”:Document 对象,表示服务器返回一个文档对象。
  • “json”:JSON 对象。
  • “text”:字符串。

XMLHttpRequest.responseText属性返回从服务器接收到的字符串,该属性为只读。只有 HTTP 请求完成接收以后,该属性才会包含完整的数据。

XMLHttpRequest.responseXML属性返回从服务器接收到的 HTML 或 XML 文档对象,该属性为只读。如果本次请求没有成功,或者收到的数据不能被解析为 XML 或 HTML,该属性等于null

该属性得到的数据,是直接解析后的文档 DOM 树。

status

XMLHttpRequest.status属性返回一个整数,表示服务器回应的 HTTP 状态码。一般来说,如果通信成功的话,这个状态码是200;如果服务器没有返回状态码,那么这个属性默认是200。请求发出之前,该属性为0。该属性只读。

  • 200, OK,访问正常
  • 301, Moved Permanently,永久移动
  • 302, Moved temporarily,暂时移动
  • 304, Not Modified,未修改
  • 307, Temporary Redirect,暂时重定向
  • 401, Unauthorized,未授权
  • 403, Forbidden,禁止访问
  • 404, Not Found,未发现指定网址
  • 500, Internal Server Error,服务器发生错误

XMLHttpRequest.timeout属性返回一个整数,表示多少毫秒后,如果请求仍然没有得到结果,就会自动终止。如果该属性等于0,就表示没有时间限制。

XMLHttpRequestEventTarget.ontimeout属性用于设置一个监听函数,如果发生 timeout 事件,就会执行这个监听函数。

var xhr = new XMLHttpRequest();
var url = '/server';xhr.ontimeout = function () {console.error('The request for ' + url + ' timed out.');
};xhr.onload = function() {if (xhr.readyState === 4) {if (xhr.status === 200) {// 处理服务器返回的数据} else {console.error(xhr.statusText);}}
};xhr.open('GET', url, true);
// 指定 10 秒钟超时
xhr.timeout = 10 * 1000;
xhr.send(null);
监听属性

XMLHttpRequest 对象可以对以下事件指定监听函数。

  • XMLHttpRequest.onloadstart:loadstart 事件(HTTP 请求发出)的监听函数
  • XMLHttpRequest.onprogress:progress事件(正在发送和加载数据)的监听函数
  • XMLHttpRequest.onabort:abort 事件(请求中止,比如用户调用了abort()方法)的监听函数
  • XMLHttpRequest.onerror:error 事件(请求失败)的监听函数
  • XMLHttpRequest.onload:load 事件(请求成功完成)的监听函数
  • XMLHttpRequest.ontimeout:timeout 事件(用户指定的时限超过了,请求还未完成)的监听函数
  • XMLHttpRequest.onloadend:loadend 事件(请求完成,不管成功或失败)的监听函数
xhr.onload = function() {var responseText = xhr.responseText;console.log(responseText);// process the response.
};xhr.onabort = function () {console.log('The request was aborted');
};xhr.onprogress = function (event) {console.log(event.loaded);console.log(event.total);
};xhr.onerror = function() {console.log('There was an error!');
};

XMLHttpRequest.withCredentials属性是一个布尔值,表示跨域请求时,用户信息(比如 Cookie 和认证的 HTTP 头信息)是否会包含在请求之中,默认为false,即向example.com发出跨域请求时,不会发送example.com设置在本机上的 Cookie(如果有的话)。

如果需要跨域 AJAX 请求发送 Cookie,需要withCredentials属性设为true。注意,同源的请求不需要设置这个属性。

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://example.com/', true);
xhr.withCredentials = true;
xhr.send(null);

为了让这个属性生效,服务器必须显式返回Access-Control-Allow-Credentials这个头信息。

Access-Control-Allow-Credentials: true

XMLHttpRequest 不仅可以发送请求,还可以发送文件,这就是 AJAX 文件上传。发送文件以后,通过XMLHttpRequest.upload属性可以得到一个对象,通过观察这个对象,可以得知上传的进展。主要方法就是监听这个对象的各种事件:loadstart、loadend、load、abort、error、progress、timeout。

XMLHttpRequest.open()方法用于指定 HTTP 请求的参数,或者说初始化 XMLHttpRequest 实例对象。它一共可以接受五个参数。

void open(string method,string url,optional boolean async,optional string user,optional string password
);
  • method:表示 HTTP 动词方法,比如GETPOSTPUTDELETEHEAD等。
  • url: 表示请求发送目标 URL。
  • async: 布尔值,表示请求是否为异步,默认为true。如果设为false,则send()方法只有等到收到服务器返回了结果,才会进行下一步操作。该参数可选。由于同步 AJAX 请求会造成浏览器失去响应,许多浏览器已经禁止在主线程使用,只允许 Worker 里面使用。所以,这个参数轻易不应该设为false
  • user:表示用于认证的用户名,默认为空字符串。该参数可选。
  • password:表示用于认证的密码,默认为空字符串。该参数可选。

注意,如果对使用过open()方法的 AJAX 请求,再次使用这个方法,等同于调用abort(),即终止请求。

下面发送 POST 请求的例子。

var xhr = new XMLHttpRequest();
xhr.open('POST', encodeURI('someURL'));

XMLHttpRequest实例方法

send()

下面是发送 POST 请求的例子。

var xhr = new XMLHttpRequest();
var data = 'email='+ encodeURIComponent(email)+ '&password='+ encodeURIComponent(password);xhr.open('POST', 'http://www.example.com', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(data);
//可以用send去人工制作提交表单,进行异步提交。
setRequestHeader()

XMLHttpRequest.setRequestHeader()方法用于设置浏览器发送的 HTTP 请求的头信息。该方法必须在open()之后、send()之前调用。如果该方法多次调用,设定同一个字段,则每一次调用的值会被合并成一个单一的值发送。

overrideMimeType()

重载MIME类型,当浏览器解析不成功,拿不到数据,为了拿到数据,可以把MIME类型修改了,把原始数据拿过来。

xhr.overrideMimeType('text/plain')
一些其他方法

getResponseHeader() , getAllResponseHeaders , abort() ,

XMLHttpRequest实例事件

readyStateChange

readystate属性发生变化时,就会触发,这个经常用 ,下面是以前项目用到的一段标准用法代码。

var xmlhttp;
xmlhttp=new XMLHttpRequest();
var name=document.getElementById("name").value;
xmlhttp.onreadystatechange = function () {if (xmlhttp.readyState == 4 && xmlhttp.status == 200){var txt = xmlhttp.responseText.toString();var obj = JSON.parse(txt);var show="该成员不属于网络技术组";for (var i = 0; i < obj.contact.length; i++) {if(obj.contact[i].name==name){show=obj.contact[i].name+"的电话号码为:"+obj.contact[i].num;break;}}alert(show);}
};
xmlhttp.open("GET", "http://222.111.111.223:8888/contacts.txt", true);//3000000002283171
xmlhttp.send();
progress事件
//可以查看上传的进度
var xhr = new XMLHttpRequest();function updateProgress (oEvent) {if (oEvent.lengthComputable) {var percentComplete = oEvent.loaded / oEvent.total;} else {console.log('无法计算进展');}
}xhr.addEventListener('progress', updateProgress);xhr.open();
load,error,abort,loadend

load 事件表示服务器传来的数据接收完毕,error 事件表示请求出错,abort 事件表示请求被中断(比如用户取消请求)。

上面三个事件会伴随一个loadend事件,表示请求结束,但不知道是否成功。

var xhr = new XMLHttpRequest();xhr.addEventListener('load', transferComplete);
xhr.addEventListener('error', transferFailed);
xhr.addEventListener('abort', transferCanceled);
xhr.addEventListener('loadend', loadEnd);xhr.open();function transferComplete() {console.log('数据接收完毕');
}function transferFailed() {console.log('数据接收出错');
}function transferCanceled() {console.log('用户取消接收');
}
function loadEnd(e) {console.log('请求结束,状态未知');
}
timeout

timeout属性一节已经讲过。

navigator.sendBeacon()

用户卸载网页时,有时需要向服务器发一些数据。由于网页关闭太快,会导致没发完,网页就关闭了。这时浏览器引入sendBeacon()方法,这时一个浏览器任务,页面关闭后继续运行。保证网页能发出去!

window.addEventListener('unload', logData, false);//Navigator.sendBeacon方法接受两个参数,第一个参数是目标服务器的 URL,第二个参数是所要发送的数据(可选),可以是任意类型(字符串、表单对象、二进制对象等等)。
function logData() {navigator.sendBeacon('/log', analyticsData);
}//该方法发送数据的 HTTP 方法是 POST,可以跨域,类似于表单提交数据。它不能指定回调函数。

同源限制

同源 = 协议,域名, 端口 都相同

目的:为了安全,防止cookie被利用。

限制范围:不同源的两个网页几乎不能进行通讯。

规避限制

cookie共享,可以通过设置domain属性使得两者同源。

iframe通讯,如果一级域名相同,可以通过domain属性解决,如果是完全不同网站拼接起来的,目前可以用两种办法解决:

  • 片段识别符(fragment identifier)
  • 跨文档通信API(Cross-document messaging)

fragment identifier 是指URL的#号后面的部分。

http://example.com/x.html#fragment#fragment,只改变片段识别符,页面不会重新刷新

具体用法如下:(还没想明白为什么,先记住)

父窗口可以把信息,写入子窗口的片段标识符。

var src = originURL + '#' + data;
document.getElementById('myIFrame').src = src;

上面代码中,父窗口把所要传递的信息,写入 iframe 窗口的片段标识符。

子窗口通过监听hashchange事件得到通知。

window.onhashchange = checkMessage;function checkMessage() {var message = window.location.hash;// ...
}

同样的,子窗口也可以改变父窗口的片段标识符。

parent.location.href = target + '#' + hash;

window.postMessage()

HTML5为了解决这个问题,引入了一个API,cross-document messaging

// 父窗口打开一个子窗口
var popup = window.open('http://bbb.com', 'title');
// 父窗口向子窗口发消息
popup.postMessage('Hello World!', 'http://bbb.com');// 子窗口向父窗口发消息
window.opener.postMessage('Nice to see you', 'http://aaa.com');// 父窗口和子窗口都可以用下面的代码,
// 监听 message 消息
window.addEventListener('message', function (e) {console.log(e.data);
},false);

message事件的参数是事件对象event,提供以下三个属性。

  • event.source:发送消息的窗口
  • event.origin: 消息发向的网址
  • event.data: 消息内容
//下面的例子是,子窗口通过event.source属性引用父窗口,然后发送消息
window.addEventListener('message', receiveMessage);
function receiveMessage(event) {event.source.postMessage('Nice to see you!', '*');
}//上面做法不安全,应该采用下面做法,更安全一些。
window.addEventListener('message', receiveMessage);
function receiveMessage(event) {if (event.origin !== 'http://aaa.com') return;if (event.data === 'Hello World') {event.source.postMessage('Hello', event.origin);} else {console.log(event.data);}
}

LocalStorage

通过window.postMessage,读写其他窗口的 LocalStorage 也成为了可能。

下面是一个例子,主窗口写入 iframe 子窗口的localStorage

window.onmessage = function(e) {if (e.origin !== 'http://bbb.com') {return;}var payload = JSON.parse(e.data);localStorage.setItem(payload.key, JSON.stringify(payload.data));
};

上面代码中,子窗口将父窗口发来的消息,写入自己的 LocalStorage。

父窗口发送消息的代码如下。

var win = document.getElementsByTagName('iframe')[0].contentWindow;
var obj = { name: 'Jack' };
win.postMessage(JSON.stringify({key: 'storage', data: obj}),'http://bbb.com'
);

加强版的子窗口接收消息的代码如下。

window.onmessage = function(e) {if (e.origin !== 'http://bbb.com') return;var payload = JSON.parse(e.data);switch (payload.method) {case 'set':localStorage.setItem(payload.key, JSON.stringify(payload.data));break;case 'get':var parent = window.parent;var data = localStorage.getItem(payload.key);parent.postMessage(data, 'http://aaa.com');break;case 'remove':localStorage.removeItem(payload.key);break;}
};

加强版的父窗口发送消息代码如下。

var win = document.getElementsByTagName('iframe')[0].contentWindow;
var obj = { name: 'Jack' };
// 存入对象
win.postMessage(JSON.stringify({key: 'storage', method: 'set', data: obj}),'http://bbb.com'
);
// 读取对象
win.postMessage(JSON.stringify({key: 'storage', method: "get"}),"*"
);
window.onmessage = function(e) {if (e.origin != 'http://aaa.com') return;console.log(JSON.parse(e.data).name);
};

AJAX

同源政策规定,AJAX 请求只能发给同源的网址,否则就报错。

除了架设服务器代理(浏览器请求同源服务器,再由后者请求外部服务),有三种方法规避这个限制。

  • JSONP
  • WebSocket
  • CORS
JSONP

JSONP 是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务端改造非常小。

它的基本思想是,网页通过添加一个<script>元素,向服务器请求 JSON 数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。

首先,网页动态插入<script>元素,由它向跨源网址发出请求。

function addScriptTag(src) {var script = document.createElement('script');script.setAttribute("type","text/javascript");script.src = src;document.body.appendChild(script);
}window.onload = function () {addScriptTag('http://example.com/ip?callback=foo');
}function foo(data) {console.log('Your public IP address is: ' + data.ip);
};

上面代码通过动态添加<script>元素,向服务器example.com发出请求。注意,该请求的查询字符串有一个callback参数,用来指定回调函数的名字,这对于 JSONP 是必需的。

服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。

foo({"ip": "8.8.8.8"
});

由于<script>元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了foo函数,该函数就会立即调用。作为参数的 JSON 数据被视为 JavaScript 对象,而不是字符串,因此避免了使用JSON.parse的步骤。

WebSocket

WebSocket 是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。

下面是一个例子,浏览器发出的 WebSocket 请求的头信息(摘自维基百科)。

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com

上面代码中,有一个字段是Origin,表示该请求的请求源(origin),即发自哪个域名。

正是因为有了Origin这个字段,所以 WebSocket 才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出如下回应。

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
CORS

CORS 是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是 W3C 标准,属于跨源 AJAX 请求的根本解决方法。相比 JSONP 只能发GET请求,CORS 允许任何类型的请求。

CORS通讯

CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能。

整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS 通信与普通的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨域,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感知。因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨域通信。

与JSONP的区别。

CORS 与 JSONP 的使用目的相同,但是比 JSONP 更强大。JSONP 只支持GET请求,CORS 支持所有类型的 HTTP 请求。JSONP 的优势在于支持老式浏览器,以及可以向不支持 CORS 的网站请求数据。

Storage接口

Storage 接口用于脚本在浏览器保存数据。两个对象部署了这个接口:window.sessionStoragewindow.localStorage

sessionStorage保存的数据用于浏览器的一次会话(session),当会话结束(通常是窗口关闭),数据被清空;localStorage保存的数据长期存在,下一次访问该网站的时候,网页可以直接读取以前保存的数据。

这个接口很像 Cookie 的强化版,能够使用大得多的存储空间。目前,每个域名的存储上限视浏览器而定,Chrome 是 2.5MB,Firefox 和 Opera 是 5MB,IE 是 10MB。其中,Firefox 的存储空间由一级域名决定,而其他浏览器没有这个限制。也就是说,Firefox 中,a.example.comb.example.com共享 5MB 的存储空间。另外,与 Cookie 一样,它们也受同域限制。某个网页存入的数据,只有同域下的网页才能读取,如果跨域操作会报错。

storage的属性

Storage 接口只有一个属性。

  • Storage.length:返回保存的数据项个数。

storage的方法

Storage.setItem()方法用于存入数据。它接受两个参数,第一个是键名,第二个是保存的数据。如果键名已经存在,该方法会更新已有的键值。该方法没有返回值

写入不一定要用这个方法,直接赋值也是可以的。

// 下面三种写法等价
window.localStorage.foo = '123';
window.localStorage['foo'] = '123';
window.localStorage.setItem('foo', '123');

Storage.getItem()方法用于读取数据。它只有一个参数,就是键名。如果键名不存在,该方法返回null

window.sessionStorage.getItem('key')
window.localStorage.getItem('key')

键名应该是一个字符串,否则会被自动转为字符串。

Storage.removeItem()方法用于清除某个键名对应的键值。它接受键名作为参数,如果键名不存在,该方法不会做任何事情。

sessionStorage.removeItem('key');
localStorage.removeItem('key');

Storage.clear()方法用于清除所有保存的数据。该方法的返回值是undefined

window.sessionStorage.clear()
window.localStorage.clear()

Storage.key()接受一个整数作为参数(从零开始),返回该位置对应的键值。

window.sessionStorage.setItem('key', 'value');
window.sessionStorage.key(0) // "key"

结合使用Storage.length属性和Storage.key()方法,可以遍历所有的键。

for (var i = 0; i < window.localStorage.length; i++) {console.log(localStorage.key(i));
}

storage事件

Storage 接口储存的数据发生变化时,会触发 storage 事件,可以指定这个事件的监听函数。

window.addEventListener('storage', onStorageChange);

监听函数接受一个event实例对象作为参数。这个实例对象继承了 StorageEvent 接口,有几个特有的属性,都是只读属性。

  • StorageEvent.key:字符串,表示发生变动的键名。如果 storage 事件是由clear()方法引起,该属性返回null
  • StorageEvent.newValue:字符串,表示新的键值。如果 storage 事件是由clear()方法或删除该键值对引发的,该属性返回null
  • StorageEvent.oldValue:字符串,表示旧的键值。如果该键值对是新增的,该属性返回null
  • StorageEvent.storageArea:对象,返回键值对所在的整个对象。也说是说,可以从这个属性上面拿到当前域名储存的所有键值对。
  • StorageEvent.url:字符串,表示原始触发 storage 事件的那个网页的网址。

下面是StorageEvent.key属性的例子。

function onStorageChange(e) {console.log(e.key);
}window.addEventListener('storage', onStorageChange);

注意,该事件有一个很特别的地方,就是它不在导致数据变化的当前页面触发,而是在同一个域名的其他窗口触发。也就是说,如果浏览器只打开一个窗口,可能观察不到这个事件。比如同时打开多个窗口,当其中的一个窗口导致储存的数据发生改变时,只有在其他窗口才能观察到监听函数的执行。可以通过这种机制,实现多个窗口之间的通信。

history对象

window.history属性指向 History 对象,它表示当前窗口的浏览历史。

History 对象保存了当前窗口访问过的所有页面网址。下面代码表示当前窗口一共访问过3个网址。

window.history.length // 3

由于安全原因,浏览器不允许脚本读取这些地址,但是允许在地址之间导航。

// 后退到前一个网址
history.back()// 等同于
history.go(-1)

浏览器工具栏的“前进”和“后退”按钮,其实就是对 History 对象进行操作。

history的属性

History 对象主要有两个属性。

  • History.length:当前窗口访问过的网址数量(包括当前网页)
  • History.state:History 堆栈最上层的状态值(详见下文)
// 当前窗口访问过多少个网页
window.history.length // 1// History 对象的当前状态
// 通常是 undefined,即未设置
window.history.state // undefined

history的方法

History.back(),History.forward(),History.go()

注意,移动到以前访问过的页面时,页面通常是从浏览器缓存之中加载,而不是重新要求服务器发送新的网页。

pushState()

作用是添加一条历史记录,相当于在history这个数组中添加一个元素,但不能实现跨域。

replaceState()

History.replaceState()方法用来修改 History 对象的当前记录,其他都与pushState()方法一模一样。

上诉两个函数的作用都是把history中的存放数组修改,push就是字面意思,replace就是改 arr[0] 的值

history事件

popstate事件,当前窗口页面变化时会触发(包括刷新),另外,该事件只针对同一文档,如果加载不同的文档,事件也不会触发。

window.onpopstate = function (event) {console.log('location: ' + document.location);console.log('state: ' + JSON.stringify(event.state));
};// 或者
window.addEventListener('popstate', function(event) {console.log('location: ' + document.location);console.log('state: ' + JSON.stringify(event.state));
});

回调函数的参数是一个event事件对象,它的state属性指向pushStatereplaceState方法为当前 URL 所提供的状态对象(即这两个方法的第一个参数)。上面代码中的event.state,就是通过pushStatereplaceState方法,为当前 URL 绑定的state对象。

这个state对象也可以直接通过history对象读取。

var currentState = history.state;

注意,页面第一次加载的时候,浏览器不会触发popstate事件

location对象

Location对象是浏览器提供的原生对象,提供 URL 相关的信息和操作方法。通过window.locationdocument.location属性,可以拿到这个对象。

location属性

Location对象提供以下属性。

  • Location.href:整个 URL。
  • Location.protocol:当前 URL 的协议,包括冒号(:)。
  • Location.host:主机,包括冒号(:)和端口(默认的80端口和443端口会省略)。
  • Location.hostname:主机名,不包括端口。
  • Location.port:端口号。
  • Location.pathname:URL 的路径部分,从根路径/开始。
  • Location.search:查询字符串部分,从问号?开始。
  • Location.hash:片段字符串部分,从#开始。
  • Location.username:域名前面的用户名。
  • Location.password:域名前面的密码。
  • Location.origin:URL 的协议、主机名和端口。
// 当前网址为
// http://user:passwd@www.example.com:4097/path/a.html?x=111#part1
document.location.href
// "http://user:passwd@www.example.com:4097/path/a.html?x=111#part1"
document.location.protocol
// "http:"
document.location.host
// "www.example.com:4097"
document.location.hostname
// "www.example.com"
document.location.port
// "4097"
document.location.pathname
// "/path/a.html"
document.location.search
// "?x=111"
document.location.hash
// "#part1"
document.location.username
// "user"
document.location.password
// "passwd"
document.location.origin
// "http://user:passwd@www.example.com:4097"

这些属性里面,只有origin属性是只读的,其他属性都可写。

注意,如果对Location.href写入新的 URL 地址,浏览器会立刻跳转到这个新地址。

// 跳转到新网址
document.location.href = 'http://www.example.com';

这个特性常常用于让网页自动滚动到新的锚点。

document.location.href = '#top';
// 等同于
document.location.hash = '#top';

直接改写location,相当于写入href属性。

document.location = 'http://www.example.com';
// 等同于
document.location.href = 'http://www.example.com';

另外,Location.href属性是浏览器唯一允许跨域写入的属性,即非同源的窗口可以改写另一个窗口(比如子窗口与父窗口)的Location.href属性,导致后者的网址跳转。Location的其他属性都不允许跨域写入。

location方法

(1)Location.assign()

assign方法接受一个 URL 字符串作为参数,使得浏览器立刻跳转到新的 URL。如果参数不是有效的 URL 字符串,则会报错。

// 跳转到新的网址
document.location.assign('http://www.example.com')

(2)Location.replace()

replace方法接受一个 URL 字符串作为参数,使得浏览器立刻跳转到新的 URL。如果参数不是有效的 URL 字符串,则会报错。

它与assign方法的差异在于,replace会在浏览器的浏览历史History里面删除当前网址,也就是说,一旦使用了该方法,后退按钮就无法回到当前网页了,相当于在浏览历史里面,使用新的 URL 替换了老的 URL。它的一个应用是,当脚本发现当前是移动设备时,就立刻跳转到移动版网页。

// 跳转到新的网址
document.location.replace('http://www.example.com')

(3)Location.reload()

reload方法使得浏览器重新加载当前网址,相当于按下浏览器的刷新按钮。

它接受一个布尔值作为参数。如果参数为true,浏览器将向服务器重新请求这个网页,并且重新加载后,网页将滚动到头部(即scrollTop === 0)。如果参数是false或为空,浏览器将从本地缓存重新加载该网页,并且重新加载后,网页的视口位置是重新加载前的位置。

// 向服务器重新请求当前网址
window.location.reload(true);

(4)Location.toString()

toString方法返回整个 URL 字符串,相当于读取Location.href属性。

URL编码与解码

网页的 URL 只能包含合法的字符。合法字符分成两类。

  • URL 元字符:分号(;),逗号(,),斜杠(/),问号(?),冒号(:),at(@),&,等号(=),加号(+),美元符号($),井号(#
  • 语义字符:a-zA-Z0-9,连词号(-),下划线(_),点(.),感叹号(!),波浪线(~),星号(*),单引号('),圆括号(()

除了以上字符,其他字符出现在 URL 之中都必须转义,规则是根据操作系统的默认编码,将每个字节转为百分号(%)加上两个大写的十六进制字母。

JavaScript 提供四个 URL 的编码/解码方法。

  • encodeURI()
  • encodeURIComponent()
  • decodeURI()
  • decodeURIComponent()

encodeURI()方法用于转码整个 URL。它的参数是一个字符串,代表整个 URL。它会将元字符和语义字符之外的字符,都进行转义。

encodeURI('http://www.example.com/q=春节')
// "http://www.example.com/q=%E6%98%A5%E8%8A%82"

encodeURIComponent()方法用于转码 URL 的组成部分,会转码除了语义字符之外的所有字符,即元字符也会被转码。所以,它不能用于转码整个 URL。它接受一个参数,就是 URL 的片段。

encodeURIComponent('春节')
// "%E6%98%A5%E8%8A%82"
encodeURIComponent('http://www.example.com/q=春节')
// "http%3A%2F%2Fwww.example.com%2Fq%3D%E6%98%A5%E8%8A%82"

decodeURI()方法用于整个 URL 的解码。它是encodeURI()方法的逆运算。它接受一个参数,就是转码后的 URL。

decodeURI('http://www.example.com/q=%E6%98%A5%E8%8A%82')
// "http://www.example.com/q=春节"

decodeURIComponent()用于URL 片段的解码。它是encodeURIComponent()方法的逆运算。它接受一个参数,就是转码后的 URL 片段。

decodeURIComponent('%E6%98%A5%E8%8A%82')
// "春节"

url对象

URL对象是浏览器的原生对象,可以用来构造、解析和编码 URL。一般情况下,通过window.URL可以拿到这个对象。

元素和

元素都部署了这个接口。这就是说,它们的 DOM 节点对象可以使用 URL 的实例属性和方法。

var a = document.createElement('a');
a.href = 'http://example.com/?foo=1';a.hostname // "example.com"
a.search // "?foo=1"

上面代码中,a<a>元素的 DOM 节点对象。可以在这个对象上使用 URL 的实例属性,比如hostnamesearch

构造函数

URL对象本身是一个构造函数,可以生成 URL 实例。

它接受一个表示 URL 的字符串作为参数。如果参数不是合法的 URL,会报错。

var url = new URL('http://www.example.com/index.html');
url.href
// "http://www.example.com/index.html"

如果参数是另一个 URL 实例,构造函数会自动读取该实例的href属性,作为实际参数。

如果 URL 字符串是一个相对路径,那么需要表示绝对路径的第二个参数,作为计算基准。

var url1 = new URL('index.html', 'http://example.com');
url1.href
// "http://example.com/index.html"var url2 = new URL('page2.html', 'http://example.com/page1.html');
url2.href
// "http://example.com/page2.html"var url3 = new URL('..', 'http://example.com/a/b.html')
url3.href
// "http://example.com/"

上面代码中,返回的 URL 实例的路径都是在第二个参数的基础上,切换到第一个参数得到的。最后一个例子里面,第一个参数是..,表示上层路径。

URL属性

URL 实例的属性与Location对象的属性基本一致,返回当前 URL 的信息。

  • URL.href:返回整个 URL
  • URL.protocol:返回协议,以冒号:结尾
  • URL.hostname:返回域名
  • URL.host:返回域名与端口,包含:号,默认的80和443端口会省略
  • URL.port:返回端口
  • URL.origin:返回协议、域名和端口
  • URL.pathname:返回路径,以斜杠/开头
  • URL.search:返回查询字符串,以问号?开头
  • URL.searchParams:返回一个URLSearchParams实例,该属性是Location对象没有的
  • URL.hash:返回片段识别符,以井号#开头
  • URL.password:返回域名前面的密码
  • URL.username:返回域名前面的用户名
var url = new URL('http://user:passwd@www.example.com:4097/path/a.html?x=111#part1');url.href
// "http://user:passwd@www.example.com:4097/path/a.html?x=111#part1"
url.protocol
// "http:"
url.hostname
// "www.example.com"
url.host
// "www.example.com:4097"
url.port
// "4097"
url.origin
// "http://www.example.com:4097"
url.pathname
// "/path/a.html"
url.search
// "?x=111"
url.searchParams
// URLSearchParams {}
url.hash
// "#part1"
url.password
// "passwd"
url.username
// "user"

这些属性里面,只有origin属性是只读的,其他属性都可写。

var url = new URL('http://example.com/index.html#part1');url.pathname = 'index2.html';
url.href // "http://example.com/index2.html#part1"url.hash = '#part2';
url.href // "http://example.com/index2.html#part2"

上面代码中,改变 URL 实例的pathname属性和hash属性,都会实时反映在 URL 实例当中。

URL方法

(1)URL.createObjectURL()

URL.createObjectURL方法用来为上传/下载的文件、流媒体文件生成一个 URL 字符串。这个字符串代表了File对象或Blob对象的 URL。

// HTML 代码如下
// <div id="display"/>
// <input
//   type="file"
//   id="fileElem"
//   multiple
//   accept="image/*"
//   onchange="handleFiles(this.files)"
//  >
var div = document.getElementById('display');function handleFiles(files) {for (var i = 0; i < files.length; i++) {var img = document.createElement('img');img.src = window.URL.createObjectURL(files[i]);div.appendChild(img);}
}

上面代码中,URL.createObjectURL方法用来为上传的文件生成一个 URL 字符串,作为<img>元素的图片来源。

该方法生成的 URL 就像下面的样子。

blob:http://localhost/c745ef73-ece9-46da-8f66-ebes574789b1

注意,每次使用URL.createObjectURL方法,都会在内存里面生成一个 URL 实例。如果不再需要该方法生成的 URL 字符串,为了节省内存,可以使用URL.revokeObjectURL()方法释放这个实例。

(2)URL.revokeObjectURL()

URL.revokeObjectURL方法用来释放URL.createObjectURL方法生成的 URL 实例。它的参数就是URL.createObjectURL方法返回的 URL 字符串。

下面为上一段的示例加上URL.revokeObjectURL()

var div = document.getElementById('display');function handleFiles(files) {for (var i = 0; i < files.length; i++) {var img = document.createElement('img');img.src = window.URL.createObjectURL(files[i]);div.appendChild(img);img.onload = function() {window.URL.revokeObjectURL(this.src);}}
}

上面代码中,一旦图片加载成功以后,为本地文件生成的 URL 字符串就没用了,于是可以在img.onload回调函数里面,通过URL.revokeObjectURL方法卸载这个 URL 实例。

URLSearchParams对象

URLSearchParams对象是浏览器的原生对象,用来构造、解析和处理 URL 的查询字符串(即 URL 问号后面的部分)。

它本身也是一个构造函数,可以生成实例。参数可以为查询字符串,起首的问号?有没有都行,也可以是对应查询字符串的数组或对象。

// 方法一:传入字符串
var params = new URLSearchParams('?foo=1&bar=2');
// 等同于
var params = new URLSearchParams(document.location.search);// 方法二:传入数组
var params = new URLSearchParams([['foo', 1], ['bar', 2]]);// 方法三:传入对象
var params = new URLSearchParams({'foo' : 1 , 'bar' : 2});

URLSearchParams会对查询字符串自动编码。

var params = new URLSearchParams({'foo': '你好'});
params.toString() // "foo=%E4%BD%A0%E5%A5%BD"

上面代码中,foo的值是汉字,URLSearchParams对其自动进行 URL 编码。

浏览器向服务器发送表单数据时,可以直接使用URLSearchParams实例作为表单数据。

const params = new URLSearchParams({foo: 1, bar: 2});
fetch('https://example.com/api', {method: 'POST',body: params
}).then(...)

上面代码中,fetch命令向服务器发送命令时,可以直接使用URLSearchParams实例。

URLSearchParams可以与URL接口结合使用。

var url = new URL(window.location);
var foo = url.searchParams.get('foo') || 'somedefault';

上面代码中,URL 实例的searchParams属性就是一个URLSearchParams实例,所以可以使用URLSearchParams接口的get方法。

DOM 的a元素节点的searchParams属性,就是一个URLSearchParams实例。

var a = document.createElement('a');
a.href = 'https://example.com?filter=api';
a.searchParams.get('filter') // "api"

URLSearchParams实例有遍历器接口,可以用for...of循环遍历(详见《ES6 标准入门》的《Iterator》一章)。

var params = new URLSearchParams({'foo': 1 , 'bar': 2});for (var p of params) {console.log(p[0] + ': ' + p[1]);
}
// foo: 1
// bar: 2

URLSearchParams没有实例属性,只有实例方法。

toString()

toString方法返回实例的字符串形式。

var url = new URL('https://example.com?foo=1&bar=2');
var params = new URLSearchParams(url.search);params.toString() // "foo=1&bar=2'

那么需要字符串的场合,会自动调用toString方法。

var params = new URLSearchParams({version: 2.0});
window.location.href = location.pathname + '?' + params;

上面代码中,location.href赋值时,可以直接使用params对象。这时就会自动调用toString方法。

append()

append方法用来追加一个查询参数。它接受两个参数,第一个为键名,第二个为键值,没有返回值。

var params = new URLSearchParams({'foo': 1 , 'bar': 2});
params.append('baz', 3);
params.toString() // "foo=1&bar=2&baz=3"

append方法不会识别是否键名已经存在。

var params = new URLSearchParams({'foo': 1 , 'bar': 2});
params.append('foo', 3);
params.toString() // "foo=1&bar=2&foo=3"

上面代码中,查询字符串里面foo已经存在了,但是append依然会追加一个同名键。

delete()

delete方法用来删除指定的查询参数。它接受键名作为参数。

var params = new URLSearchParams({'foo': 1 , 'bar': 2});
params.delete('bar');
params.toString() // "foo=1"

has()

has方法返回一个布尔值,表示查询字符串是否包含指定的键名。

var params = new URLSearchParams({'foo': 1 , 'bar': 2});
params.has('bar') // true
params.has('baz') // false

set()

set方法用来设置查询字符串的键值。

它接受两个参数,第一个是键名,第二个是键值。如果是已经存在的键,键值会被改写,否则会被追加。

var params = new URLSearchParams('?foo=1');
params.set('foo', 2);
params.toString() // "foo=2"
params.set('bar', 3);
params.toString() // "foo=2&bar=3"

上面代码中,foo是已经存在的键,bar是还不存在的键。

如果有多个的同名键,set会移除现存所有的键。

var params = new URLSearchParams('?foo=1&foo=2');
params.set('foo', 3);
params.toString() // "foo=3"

下面是一个替换当前 URL 的例子。

// URL: https://example.com?version=1.0
var params = new URLSearchParams(location.search.slice(1));
params.set('version', 2.0);window.history.replaceState({}, '', location.pathname + `?` + params);
// URL: https://example.com?version=2.0

get(),getAll()

get方法用来读取查询字符串里面的指定键。它接受键名作为参数。

var params = new URLSearchParams('?foo=1');
params.get('foo') // "1"
params.get('bar') // null

两个地方需要注意。第一,它返回的是字符串,如果原始值是数值,需要转一下类型;第二,如果指定的键名不存在,返回值是null

如果有多个的同名键,get返回位置最前面的那个键值。

var params = new URLSearchParams('?foo=3&foo=2&foo=1');
params.get('foo') // "3"

上面代码中,查询字符串有三个foo键,get方法返回最前面的键值3

getAll方法返回一个数组,成员是指定键的所有键值。它接受键名作为参数。

var params = new URLSearchParams('?foo=1&foo=2');
params.getAll('foo') // ["1", "2"]

上面代码中,查询字符串有两个foo键,getAll返回的数组就有两个成员。

sort()

sort方法对查询字符串里面的键进行排序,规则是按照 Unicode 码点从小到大排列。

该方法没有返回值,或者说返回值是undefined

var params = new URLSearchParams('c=4&a=2&b=3&a=1');
params.sort();
params.toString() // "a=2&a=1&b=3&c=4"

上面代码中,如果有两个同名的键a,它们之间不会排序,而是保留原始的顺序。

keys(),values(),entries()

这三个方法都返回一个遍历器对象,供for...of循环消费。它们的区别在于,keys方法返回的是键名的遍历器,values方法返回的是键值的遍历器,entries返回的是键值对的遍历器。

var params = new URLSearchParams('a=1&b=2');for(var p of params.keys()) {console.log(p);
}
// a
// bfor(var p of params.values()) {console.log(p);
}
// 1
// 2for(var p of params.entries()) {console.log(p);
}
// ["a", "1"]
// ["b", "2"]

如果直接对URLSearchParams进行遍历,其实内部调用的就是entries接口。

for (var p of params) {}
// 等同于
for (var p of params.entries()) {}

ArrayBuffer对象,Blob对象

Blob:binary large object

ArrayBuffer对象

ArrayBuffer 对象表示一段二进制数据,用来模拟内存里面的数据。通过这个对象,JavaScript 可以读写二进制数据。这个对象可以看作内存数据的表达。

这个对象是 ES6 才写入标准的,普通的网页编程用不到它,为了教程体系的完整,下面只提供一个简略的介绍,详细介绍请看《ES6 标准入门》里面的章节。

浏览器原生提供ArrayBuffer()构造函数,用来生成实例。它接受一个整数作为参数,表示这段二进制数据占用多少个字节。

var buffer = new ArrayBuffer(8);

上面代码中,实例对象buffer占用8个字节。

ArrayBuffer 对象有实例属性byteLength,表示当前实例占用的内存长度(单位字节)。

var buffer = new ArrayBuffer(8);
buffer.byteLength // 8

ArrayBuffer 对象有实例方法slice(),用来复制一部分内存。它接受两个整数参数,分别表示复制的开始位置(从0开始)和结束位置(复制时不包括结束位置),如果省略第二个参数,则表示一直复制到结束。

var buf1 = new ArrayBuffer(8);
var buf2 = buf1.slice(0);

上面代码表示复制原来的实例

Blob对象

Blob 对象表示一个二进制文件的数据内容,比如一个图片文件的内容就可以通过 Blob 对象读写。它通常用来读写文件,它的名字是 Binary Large Object (二进制大型对象)的缩写。它与 ArrayBuffer 的区别在于,它用于操作二进制文件,而 ArrayBuffer 用于操作内存。

浏览器原生提供Blob()构造函数,用来生成实例对象。

new Blob(array [, options])

Blob构造函数接受两个参数。第一个参数是数组,成员是字符串或二进制对象,表示新生成的Blob实例对象的内容;第二个参数是可选的,是一个配置对象,目前只有一个属性type,它的值是一个字符串,表示数据的 MIME 类型,默认是空字符串。

var htmlFragment = ['<a id="a"><b id="b">hey!</b></a>'];
var myBlob = new Blob(htmlFragment, {type : 'text/html'});

上面代码中,实例对象myBlob包含的是字符串。生成实例的时候,数据类型指定为text/html

下面是另一个例子,Blob 保存 JSON 数据。

var obj = { hello: 'world' };
var blob = new Blob([ JSON.stringify(obj) ], {type : 'application/json'});
属性与方法

Blob具有两个实例属性sizetype,分别返回数据的大小和类型。

var htmlFragment = ['<a id="a"><b id="b">hey!</b></a>'];
var myBlob = new Blob(htmlFragment, {type : 'text/html'});myBlob.size // 32
myBlob.type // "text/html"

Blob具有一个实例方法slice,用来拷贝原来的数据,返回的也是一个Blob实例。

myBlob.slice(start,end, contentType)

slice方法有三个参数,都是可选的。它们依次是起始的字节位置(默认为0)、结束的字节位置(默认为size属性的值,该位置本身将不包含在拷贝的数据之中)、新实例的数据类型(默认为空字符串)。

获取文件信息

文件选择器<input type="file">用来让用户选取文件。出于安全考虑,浏览器不允许脚本自行设置这个控件的value属性,即文件必须是用户手动选取的,不能是脚本指定的。一旦用户选好了文件,脚本就可以读取这个文件。

文件选择器返回一个 FileList 对象,该对象是一个类似数组的成员,每个成员都是一个 File 实例对象。File 实例对象是一个特殊的 Blob 实例,增加了namelastModifiedDate属性。

// HTML 代码如下
// <input type="file" accept="image/*" multiple οnchange="fileinfo(this.files)"/>function fileinfo(files) {for (var i = 0; i < files.length; i++) {var f = files[i];console.log(f.name, // 文件名,不含路径f.size, // 文件大小,Blob 实例属性f.type, // 文件类型,Blob 实例属性f.lastModifiedDate // 文件的最后修改时间);}
}

除了文件选择器,拖放 API 的dataTransfer.files返回的也是一个FileList 对象,它的成员因此也是 File 实例对象。

下载文件

AJAX 请求时,如果指定responseType属性为blob,下载下来的就是一个 Blob 对象。

function getBlob(url, callback) {var xhr = new XMLHttpRequest();xhr.open('GET', url);xhr.responseType = 'blob';xhr.onload = function () {callback(xhr.response);}xhr.send(null);
}

上面代码中,xhr.response拿到的就是一个 Blob 对象。

生成URL

浏览器允许使用URL.createObjectURL()方法,针对 Blob 对象生成一个临时 URL,以便于某些 API 使用。这个 URL 以blob://开头,表明对应一个 Blob 对象,协议头后面是一个识别符,用来唯一对应内存里面的 Blob 对象。这一点与data://URL(URL 包含实际数据)和file://URL(本地文件系统里面的文件)都不一样。

var droptarget = document.getElementById('droptarget');droptarget.ondrop = function (e) {var files = e.dataTransfer.files;for (var i = 0; i < files.length; i++) {var type = files[i].type;if (type.substring(0,6) !== 'image/')continue;var img = document.createElement('img');img.src = URL.createObjectURL(files[i]);img.onload = function () {this.width = 100;document.body.appendChild(this);URL.revokeObjectURL(this.src);}}
}

上面代码通过为拖放的图片文件生成一个 URL,产生它们的缩略图,从而使得用户可以预览选择的文件。

浏览器处理 Blob URL 就跟普通的 URL 一样,如果 Blob 对象不存在,返回404状态码;如果跨域请求,返回403状态码。Blob URL 只对 GET 请求有效,如果请求成功,返回200状态码。由于 Blob URL 就是普通 URL,因此可以下载。

读取文件

取得 Blob 对象以后,可以通过FileReader对象,读取 Blob 对象的内容,即文件内容。

FileReader 对象提供四个方法,处理 Blob 对象。Blob 对象作为参数传入这些方法,然后以指定的格式返回。

  • FileReader.readAsText():返回文本,需要指定文本编码,默认为 UTF-8。
  • FileReader.readAsArrayBuffer():返回 ArrayBuffer 对象。
  • FileReader.readAsDataURL():返回 Data URL。
  • FileReader.readAsBinaryString():返回原始的二进制字符串。

下面是FileReader.readAsText()方法的例子,用来读取文本文件。

// HTML 代码如下
// <input type=’file' onchange='readfile(this.files[0])'></input>
// <pre id='output'></pre>
function readfile(f) {var reader = new FileReader();reader.readAsText(f);reader.onload = function () {var text = reader.result;var out = document.getElementById('output');out.innerHTML = '';out.appendChild(document.createTextNode(text));}reader.onerror = function(e) {console.log('Error', e);};
}

上面代码中,通过指定 FileReader 实例对象的onload监听函数,在实例的result属性上拿到文件内容。

下面是FileReader.readAsArrayBuffer()方法的例子,用于读取二进制文件。

// HTML 代码如下
// <input type="file" οnchange="typefile(this.files[0])"></input>
function typefile(file) {// 文件开头的四个字节,生成一个 Blob 对象var slice = file.slice(0, 4);var reader = new FileReader();// 读取这四个字节reader.readAsArrayBuffer(slice);reader.onload = function (e) {var buffer = reader.result;// 将这四个字节的内容,视作一个32位整数var view = new DataView(buffer);var magic = view.getUint32(0, false);// 根据文件的前四个字节,判断它的类型switch(magic) {case 0x89504E47: file.verified_type = 'image/png'; break;case 0x47494638: file.verified_type = 'image/gif'; break;case 0x25504446: file.verified_type = 'application/pdf'; break;case 0x504b0304: file.verified_type = 'application/zip'; break;}console.log(file.name, file.verified_type);};
}

File对象,FileList对象,FileReader对象

File对象

File 对象代表一个文件,用来读写文件信息。它继承了 Blob 对象,或者说是一种特殊的 Blob 对象,所有可以使用 Blob 对象的场合都可以使用它。

最常见的使用场合是表单的文件上传控件(<input type="file">),用户选中文件以后,浏览器就会生成一个数组,里面是每一个用户选中的文件,它们都是 File 实例对象。

// HTML 代码如下
// <input id="fileItem" type="file">
var file = document.getElementById('fileItem').files[0];
file instanceof File // true

上面代码中,file是用户选中的第一个文件,它是 File 的实例。

构造函数

浏览器原生提供一个File()构造函数,用来生成 File 实例对象。

new File(array, name [, options])

File()构造函数接受三个参数。

  • array:一个数组,成员可以是二进制对象或字符串,表示文件的内容。
  • name:字符串,表示文件名或文件路径。
  • options:配置对象,设置实例的属性。该参数可选。

第三个参数配置对象,可以设置两个属性。

  • type:字符串,表示实例对象的 MIME 类型,默认值为空字符串。
  • lastModified:时间戳,表示上次修改的时间,默认为Date.now()

下面是一个例子。

var file = new File(['foo'],'foo.txt',{type: 'text/plain',}
);

实例属性与方法

File 对象有以下实例属性。

  • File.lastModified:最后修改时间
  • File.name:文件名或文件路径
  • File.size:文件大小(单位字节)
  • File.type:文件的 MIME 类型
var myFile = new File([], 'file.bin', {lastModified: new Date(2018, 1, 1),
});
myFile.lastModified // 1517414400000
myFile.name // "file.bin"
myFile.size // 0
myFile.type // ""

上面代码中,由于myFile的内容为空,也没有设置 MIME 类型,所以size属性等于0,type属性等于空字符串。

File 对象没有自己的实例方法,由于继承了 Blob 对象,因此可以使用 Blob 的实例方法slice()

FileLisr对象

FileList对象是一个类似数组的对象,代表一组选中的文件,每个成员都是一个 File 实例。它主要出现在两个场合。

  • 文件控件节点(<input type="file">)的files属性,返回一个 FileList 实例。
  • 拖拉一组文件时,目标区的DataTransfer.files属性,返回一个 FileList 实例。
// HTML 代码如下
// <input id="fileItem" type="file">
var files = document.getElementById('fileItem').files;
files instanceof FileList // true

上面代码中,文件控件的files属性是一个 FileList 实例。

FileList 的实例属性主要是length,表示包含多少个文件。

FileList 的实例方法主要是item(),用来返回指定位置的实例。它接受一个整数作为参数,表示位置的序号(从零开始)。但是,由于 FileList 的实例是一个类似数组的对象,可以直接用方括号运算符,即myFileList[0]等同于myFileList.item(0),所以一般用不到item()方法。

FileReader对象

FileReader 对象用于读取 File 对象或 Blob 对象所包含的文件内容。

浏览器原生提供一个FileReader构造函数,用来生成 FileReader 实例。

var reader = new FileReader();

FileReader 有以下的实例属性。

  • FileReader.error:读取文件时产生的错误对象
  • FileReader.readyState:整数,表示读取文件时的当前状态。一共有三种可能的状态,0表示尚未加载任何数据,1表示数据正在加载,2表示加载完成。
  • FileReader.result:读取完成后的文件内容,有可能是字符串,也可能是一个 ArrayBuffer 实例。
  • FileReader.onabort:abort事件(用户终止读取操作)的监听函数。
  • FileReader.onerror:error事件(读取错误)的监听函数。
  • FileReader.onload:load事件(读取操作完成)的监听函数,通常在这个函数里面使用result属性,拿到文件内容。
  • FileReader.onloadstart:loadstart事件(读取操作开始)的监听函数。
  • FileReader.onloadend:loadend事件(读取操作结束)的监听函数。
  • FileReader.onprogress:progress事件(读取操作进行中)的监听函数。

下面是监听load事件的一个例子。

// HTML 代码如下
// <input type="file" onchange="onChange(event)">function onChange(event) {var file = event.target.files[0];var reader = new FileReader();reader.onload = function (event) {console.log(event.target.result)};reader.readAsText(file);
}

上面代码中,每当文件控件发生变化,就尝试读取第一个文件。如果读取成功(load事件发生),就打印出文件内容。

FileReader 有以下实例方法。

  • FileReader.abort():终止读取操作,readyState属性将变成2
  • FileReader.readAsArrayBuffer():以 ArrayBuffer 的格式读取文件,读取完成后result属性将返回一个 ArrayBuffer 实例。
  • FileReader.readAsBinaryString():读取完成后,result属性将返回原始的二进制字符串。
  • FileReader.readAsDataURL():读取完成后,result属性将返回一个 Data URL 格式(Base64 编码)的字符串,代表文件内容。对于图片文件,这个字符串可以用于<img>元素的src属性。注意,这个字符串不能直接进行 Base64 解码,必须把前缀data:*/*;base64,从字符串里删除以后,再进行解码。
  • FileReader.readAsText():读取完成后,result属性将返回文件内容的文本字符串。该方法的第一个参数是代表文件的 Blob 实例,第二个参数是可选的,表示文本编码,默认为 UTF-8。

下面是一个例子。

/* HTML 代码如下<input type="file" onchange="previewFile()"><img src="" height="200">
*/function previewFile() {var preview = document.querySelector('img');var file    = document.querySelector('input[type=file]').files[0];var reader  = new FileReader();reader.addEventListener('load', function () {preview.src = reader.result;}, false);if (file) {reader.readAsDataURL(file);}
}

上面代码中,用户选中图片文件以后,脚本会自动读取文件内容,然后作为一个 Data URL 赋值给<img>元素的src属性,从而把图片展示出来。

表单,FormData对象

表单概述

表单(<form>)用来收集用户提交的数据,发送到服务器。比如,用户提交用户名和密码,让服务器验证,就要通过表单。表单提供多种控件,让开发者使用,具体的控件种类和用法请参考 HTML 语言的教程。本章主要介绍 JavaScript 与表单的交互。

<form action="/handling-page" method="post"><div><label for="name">用户名:</label><input type="text" id="name" name="user_name" /></div><div><label for="passwd">密码:</label><input type="password" id="passwd" name="user_passwd" /></div><div><input type="submit" id="submit" name="submit_button" value="提交" /></div>
</form>

上面代码就是一个简单的表单,包含三个控件:用户名输入框、密码输入框和提交按钮。

用户点击“提交”按钮,每一个控件都会生成一个键值对,键名是控件的name属性,键值是控件的value属性,键名和键值之间由等号连接。比如,用户名输入框的name属性是user_namevalue属性是用户输入的值,假定是“张三”,提交到服务器的时候,就会生成一个键值对user_name=张三

所有的键值对都会提交到服务器。但是,提交的数据格式跟<form>元素的method属性有关。该属性指定了提交数据的 HTTP 方法。如果是 GET 方法,所有键值对会以 URL 的查询字符串形式,提交到服务器,比如/handling-page?user_name=张三&user_passwd=123&submit_button=提交。下面就是 GET 请求的 HTTP 头信息。

GET /handling-page?user_name=张三&user_passwd=123&submit_button=提交
Host: example.com

如果是 POST 方法,所有键值对会连接成一行,作为 HTTP 请求的数据体发送到服务器,比如user_name=张三&user_passwd=123&submit_button=提交。下面就是 POST 请求的头信息。

POST /handling-page HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 74user_name=张三&user_passwd=123&submit_button=提交

注意,实际提交的时候,只要键值不是 URL 的合法字符(比如汉字“张三”和“确定”),浏览器会自动对其进行编码。

点击submit控件,就可以提交表单。

<form><input type="submit" value="提交">
</form>

上面表单就包含一个submit控件,点击这个控件,浏览器就会把表单数据向服务器提交。

注意,表单里面的<button>元素如果没有用type属性指定类型,那么默认就是submit控件。

<form><button>提交</button>
</form>

上面表单的<button>元素,点击以后也会提交表单。

除了点击submit控件提交表单,还可以用表单元素的submit()方法,通过脚本提交表单。

formElement.submit();

表单元素的reset()方法可以重置所有控件的值(重置为默认值)。

formElement.reset()

FormData 对象

概述

表单数据以键值对的形式向服务器发送,这个过程是浏览器自动完成的。但是有时候,我们希望通过脚本完成过程,构造和编辑表单键值对,然后通过XMLHttpRequest.send()方法发送。浏览器原生提供了 FormData 对象来完成这项工作。

FormData 首先是一个构造函数,用来生成实例。

var formdata = new FormData(form);

FormData()构造函数的参数是一个表单元素,这个参数是可选的。如果省略参数,就表示一个空的表单,否则就会处理表单元素里面的键值对。

下面是一个表单。

<form id="myForm" name="myForm"><div><label for="username">用户名:</label><input type="text" id="username" name="username"></div><div><label for="useracc">账号:</label><input type="text" id="useracc" name="useracc"></div><div><label for="userfile">上传文件:</label><input type="file" id="userfile" name="userfile"></div>
<input type="submit" value="Submit!">
</form>

我们用 FormData 对象处理上面这个表单。

var myForm = document.getElementById('myForm');
var formData = new FormData(myForm);// 获取某个控件的值
formData.get('username') // ""// 设置某个控件的值
formData.set('username', '张三');formData.get('username') // "张三"

实例方法

FormData 提供以下实例方法。

  • FormData.get(key):获取指定键名对应的键值,参数为键名。如果有多个同名的键值对,则返回第一个键值对的键值。
  • FormData.getAll(key):返回一个数组,表示指定键名对应的所有键值。如果有多个同名的键值对,数组会包含所有的键值。
  • FormData.set(key, value):设置指定键名的键值,参数为键名。如果键名不存在,会添加这个键值对,否则会更新指定键名的键值。如果第二个参数是文件,还可以使用第三个参数,表示文件名。
  • FormData.delete(key):删除一个键值对,参数为键名。
  • FormData.append(key, value):添加一个键值对。如果键名重复,则会生成两个相同键名的键值对。如果第二个参数是文件,还可以使用第三个参数,表示文件名。
  • FormData.has(key):返回一个布尔值,表示是否具有该键名的键值对。
  • FormData.keys():返回一个遍历器对象,用于for...of循环遍历所有的键名。
  • FormData.values():返回一个遍历器对象,用于for...of循环遍历所有的键值。
  • FormData.entries():返回一个遍历器对象,用于for...of循环遍历所有的键值对。如果直接用for...of循环遍历 FormData 实例,默认就会调用这个方法。

下面是get()getAll()set()append()方法的例子。

var formData = new FormData();formData.set('username', '张三');
formData.append('username', '李四');
formData.get('username') // "张三"
formData.getAll('username') // ["张三", "李四"]formData.append('userpic[]', myFileInput.files[0], 'user1.jpg');
formData.append('userpic[]', myFileInput.files[1], 'user2.jpg');

下面是遍历器的例子。

var formData = new FormData();
formData.append('key1', 'value1');
formData.append('key2', 'value2');for (var key of formData.keys()) {console.log(key);
}
// "key1"
// "key2"for (var value of formData.values()) {console.log(value);
}
// "value1"
// "value2"for (var pair of formData.entries()) {console.log(pair[0] + ': ' + pair[1]);
}
// key1: value1
// key2: value2// 等同于遍历 formData.entries()
for (var pair of formData) {console.log(pair[0] + ': ' + pair[1]);
}
// key1: value1
// key2: value2

表单的内置验证

自动校验

表单提交的时候,浏览器允许开发者指定一些条件,它会自动验证各个表单控件的值是否符合条件。

<!-- 必填 -->
<input required><!-- 必须符合正则表达式 -->
<input pattern="banana|cherry"><!-- 字符串长度必须为6个字符 -->
<input minlength="6" maxlength="6"><!-- 数值必须在1到10之间 -->
<input type="number" min="1" max="10"><!-- 必须填入 Email 地址 -->
<input type="email"><!-- 必须填入 URL -->
<input type="URL">

如果一个控件通过验证,它就会匹配:valid的 CSS 伪类,浏览器会继续进行表单提交的流程。如果没有通过验证,该控件就会匹配:invalid的 CSS 伪类,浏览器会终止表单提交,并显示一个错误信息。

checkValidity()

除了提交表单的时候,浏览器自动校验表单,还可以手动触发表单的校验。表单元素和表单控件都有checkValidity()方法,用于手动触发校验。

// 触发整个表单的校验
form.checkValidity()// 触发单个表单控件的校验
formControl.checkValidity()

checkValidity()方法返回一个布尔值,true表示通过校验,false表示没有通过校验。因此,提交表单可以封装为下面的函数。

function submitForm(action) {var form = document.getElementById('form');form.action = action;if (form.checkValidity()) {form.submit();}
}

willValidate 属性

控件元素的willValidate属性是一个布尔值,表示该控件是否会在提交时进行校验。

// HTML 代码如下
// <form novalidate>
//   <input id="name" name="name" required />
// </form>var input = document.querySelector('#name');
input.willValidate // true

validationMessage 属性

控件元素的validationMessage属性返回一个字符串,表示控件不满足校验条件时,浏览器显示的提示文本。以下两种情况,该属性返回空字符串。

  • 该控件不会在提交时自动校验
  • 该控件满足校验条件
// HTML 代码如下
// <form><input type="text" required></form>
document.querySelector('form input').validationMessage
// "请填写此字段。"

下面是另一个例子。

var myInput = document.getElementById('myinput');
if (!myInput.checkValidity()) {document.getElementById('prompt').innerHTML = myInput.validationMessage;
}

setCustomValidity()

控件元素的setCustomValidity()方法用来定制校验失败时的报错信息。它接受一个字符串作为参数,该字符串就是定制的报错信息。如果参数为空字符串,则上次设置的报错信息被清除。

如果调用这个方法,并且参数不为空字符串,浏览器就会认为控件没有通过校验,就会立刻显示该方法设置的报错信息。

/* HTML 代码如下
<form><p><input type="file" id="fs"></p><p><input type="submit"></p>
</form>
*/document.getElementById('fs').onchange = checkFileSize;function checkFileSize() {var fs = document.getElementById('fs');var files = fs.files;if (files.length > 0) {if (files[0].size > 75 * 1024) {fs.setCustomValidity('文件不能大于 75KB');return;}}fs.setCustomValidity('');
}

上面代码一旦发现文件大于 75KB,就会设置校验失败,同时给出自定义的报错信息。然后,点击提交按钮时,就会显示报错信息。这种校验失败是不会自动消除的,所以如果所有文件都符合条件,要将报错信息设为空字符串,手动消除校验失败的状态。

validity 属性

控件元素的属性validity属性返回一个ValidityState对象,包含当前校验状态的信息。

该对象有以下属性,全部为只读属性。

  • ValidityState.badInput:布尔值,表示浏览器是否不能将用户的输入转换成正确的类型,比如用户在数值框里面输入字符串。
  • ValidityState.customError:布尔值,表示是否已经调用setCustomValidity()方法,将校验信息设置为一个非空字符串。
  • ValidityState.patternMismatch:布尔值,表示用户输入的值是否不满足模式的要求。
  • ValidityState.rangeOverflow:布尔值,表示用户输入的值是否大于最大范围。
  • ValidityState.rangeUnderflow:布尔值,表示用户输入的值是否小于最小范围。
  • ValidityState.stepMismatch:布尔值,表示用户输入的值不符合步长的设置(即不能被步长值整除)。
  • ValidityState.tooLong:布尔值,表示用户输入的字数超出了最长字数。
  • ValidityState.tooShort:布尔值,表示用户输入的字符少于最短字数。
  • ValidityState.typeMismatch:布尔值,表示用户填入的值不符合类型要求(主要是类型为 Email 或 URL 的情况)。
  • ValidityState.valid:布尔值,表示用户是否满足所有校验条件。
  • ValidityState.valueMissing:布尔值,表示用户没有填入必填的值。

下面是一个例子。

var input = document.getElementById('myinput');
if (input.validity.valid) {console.log('通过校验');
} else {console.log('校验失败');
}

下面是另外一个例子。

var txt = '';
if (document.getElementById('myInput').validity.rangeOverflow) {txt = '数值超过上限';
}
document.getElementById('prompt').innerHTML = txt;

表单的 novalidate 属性

表单元素的 HTML 属性novalidate,可以关闭浏览器的自动校验。

<form novalidate>
</form>

这个属性也可以在脚本里设置。

form.noValidate = true;

如果表单元素没有设置novalidate属性,那么提交按钮(<button><input>元素)的formnovalidate属性也有同样的作用。

<form><input type="submit" value="submit" formnovalidate>
</form>

enctype 属性

表单能够用四种编码,向服务器发送数据。编码格式由表单的enctype属性决定。

假定表单有两个字段,分别是foobaz,其中foo字段的值等于barbaz字段的值是一个分为两行的字符串。

The first line.
The second line.

下面四种格式,都可以将这个表单发送到服务器。

(1)GET 方法

如果表单使用GET方法发送数据,enctype属性无效。

<formaction="register.php"method="get"onsubmit="AJAXSubmit(this); return false;"
>
</form>

数据将以 URL 的查询字符串发出。

?foo=bar&baz=The%20first%20line.%0AThe%20second%20line.

(2)application/x-www-form-urlencoded

如果表单用POST方法发送数据,并省略enctype属性,那么数据以application/x-www-form-urlencoded格式发送(因为这是默认值)。

<formaction="register.php"method="post"onsubmit="AJAXSubmit(this); return false;"
>
</form>

发送的 HTTP 请求如下。

Content-Type: application/x-www-form-urlencodedfoo=bar&baz=The+first+line.%0D%0AThe+second+line.%0D%0A

上面代码中,数据体里面的%0D%0A代表换行符(\r\n)。

(3)text/plain

如果表单使用POST方法发送数据,enctype属性为text/plain,那么数据将以纯文本格式发送。

<formaction="register.php"method="post"enctype="text/plain"onsubmit="AJAXSubmit(this); return false;"
>
</form>

发送的 HTTP 请求如下。

Content-Type: text/plainfoo=bar
baz=The first line.
The second line.

(4)multipart/form-data

如果表单使用POST方法,enctype属性为multipart/form-data,那么数据将以混合的格式发送。

<formaction="register.php"method="post"enctype="multipart/form-data"onsubmit="AJAXSubmit(this); return false;"
>
</form>

发送的 HTTP 请求如下。

Content-Type: multipart/form-data; boundary=---------------------------314911788813839-----------------------------314911788813839
Content-Disposition: form-data; name="foo"bar
-----------------------------314911788813839
Content-Disposition: form-data; name="baz"The first line.
The second line.-----------------------------314911788813839--

这种格式也是文件上传的格式。

文件上传

用户上传文件,也是通过表单。具体来说,就是通过文件输入框选择本地文件,提交表单的时候,浏览器就会把这个文件发送到服务器。

<input type="file" id="file" name="myFile">

此外,还需要将表单<form>元素的method属性设为POSTenctype属性设为multipart/form-data。其中,enctype属性决定了 HTTP 头信息的Content-Type字段的值,默认情况下这个字段的值是application/x-www-form-urlencoded,但是文件上传的时候要改成multipart/form-data

<form method="post" enctype="multipart/form-data"><div><label for="file">选择一个文件</label><input type="file" id="file" name="myFile" multiple></div><div><input type="submit" id="submit" name="submit_button" value="上传" /></div>
</form>

上面的 HTML 代码中,file 控件的multiple属性,指定可以一次选择多个文件;如果没有这个属性,则一次只能选择一个文件。

var fileSelect = document.getElementById('file');
var files = fileSelect.files;

然后,新建一个 FormData 实例对象,模拟发送到服务器的表单数据,把选中的文件添加到这个对象上面。

var formData = new FormData();for (var i = 0; i < files.length; i++) {var file = files[i];// 只上传图片文件if (!file.type.match('image.*')) {continue;}formData.append('photos[]', file, file.name);
}

最后,使用 Ajax 向服务器上传文件。

var xhr = new XMLHttpRequest();xhr.open('POST', 'handler.php', true);xhr.onload = function () {if (xhr.status !== 200) {console.log('An error occurred!');}
};xhr.send(formData);

除了发送 FormData 实例,也可以直接 AJAX 发送文件。

var file = document.getElementById('test-input').files[0];
var xhr = new XMLHttpRequest();xhr.open('POST', 'myserver/uploads');
xhr.setRequestHeader('Content-Type', file.type);
xhr.send(file);

IndexedDB API

可以在用户本地建立一个数据库,类似nosql,然后就是增删改查的操作…

Web Worker

基本用法:

主线程采用new命令,调用Worker()构造函数,新建一个 Worker 线程。

var worker = new Worker('work.js');

Worker()构造函数的参数是一个脚本文件,该文件就是 Worker 线程所要执行的任务。由于 Worker 不能读取本地文件,所以这个脚本必须来自网络。如果下载没有成功(比如404错误),Worker 就会默默地失败。

然后,主线程调用worker.postMessage()方法,向 Worker 发消息。

worker.postMessage('Hello World');
worker.postMessage({method: 'echo', args: ['Work']});

worker.postMessage()方法的参数,就是主线程传给 Worker 的数据。它可以是各种数据类型,包括二进制数据。

接着,主线程通过worker.onmessage指定监听函数,接收子线程发回来的消息。

worker.onmessage = function (event) {doSomething(event.data);
}function doSomething() {// 执行任务worker.postMessage('Work done!');
}

上面代码中,事件对象的data属性可以获取 Worker 发来的数据。

Worker 完成任务以后,主线程就可以把它关掉。

worker.terminate();

worker线程

Worker 线程内部需要有一个监听函数,监听message事件。

self.addEventListener('message', function (e) {self.postMessage('You said: ' + e.data);
}, false);

上面代码中,self代表子线程自身,即子线程的全局对象。因此,等同于下面两种写法。

// 写法一
this.addEventListener('message', function (e) {this.postMessage('You said: ' + e.data);
}, false);// 写法二
addEventListener('message', function (e) {postMessage('You said: ' + e.data);
}, false);

除了使用self.addEventListener()指定监听函数,也可以使用self.onmessage指定。监听函数的参数是一个事件对象,它的data属性包含主线程发来的数据。self.postMessage()方法用来向主线程发送消息。

根据主线程发来的数据,Worker 线程可以调用不同的方法,下面是一个例子。

self.addEventListener('message', function (e) {var data = e.data;switch (data.cmd) {case 'start':self.postMessage('WORKER STARTED: ' + data.msg);break;case 'stop':self.postMessage('WORKER STOPPED: ' + data.msg);self.close(); // Terminates the worker.break;default:self.postMessage('Unknown command: ' + data.msg);};
}, false);

上面代码中,self.close()用于在 Worker 内部关闭自身。

work加载脚本

Worker 内部如果要加载其他脚本,有一个专门的方法importScripts()

importScripts('script1.js');

该方法可以同时加载多个脚本。

importScripts('script1.js', 'script2.js');

错误处理

主线程可以监听 Worker 是否发生错误。如果发生错误,Worker 会触发主线程的error事件。

worker.onerror(function (event) {console.log(['ERROR: Line ', event.lineno, ' in ', event.filename, ': ', event.message].join(''));
});// 或者
worker.addEventListener('error', function (event) {// ...
});

Worker 内部也可以监听error事件。

关闭worker

使用完毕,为了节省系统资源,必须关闭 Worker。

// 主线程
worker.terminate();// Worker 线程
self.close();

worker与主线程之间的数据通信

前面说过,主线程与 Worker 之间的通信内容,可以是文本,也可以是对象。需要注意的是,这种通信是拷贝关系,即是传值而不是传址,Worker 对通信内容的修改,不会影响到主线程。事实上,浏览器内部的运行机制是,先将通信内容串行化,然后把串行化后的字符串发给 Worker,后者再将它还原。

主线程与 Worker 之间也可以交换二进制数据,比如 File、Blob、ArrayBuffer 等类型,也可以在线程之间发送。下面是一个例子。

// 主线程
var uInt8Array = new Uint8Array(new ArrayBuffer(10));
for (var i = 0; i < uInt8Array.length; ++i) {uInt8Array[i] = i * 2; // [0, 2, 4, 6, 8,...]
}
worker.postMessage(uInt8Array);// Worker 线程
self.onmessage = function (e) {var uInt8Array = e.data;postMessage('Inside worker.js: uInt8Array.toString() = ' + uInt8Array.toString());postMessage('Inside worker.js: uInt8Array.byteLength = ' + uInt8Array.byteLength);
};

但是,拷贝方式发送二进制数据,会造成性能问题。比如,主线程向 Worker 发送一个 500MB 文件,默认情况下浏览器会生成一个原文件的拷贝。为了解决这个问题,JavaScript 允许主线程把二进制数据直接转移给子线程,但是一旦转移,主线程就无法再使用这些二进制数据了,这是为了防止出现多个线程同时修改数据的麻烦局面。这种转移数据的方法,叫做Transferable Objects。这使得主线程可以快速把数据交给 Worker,对于影像处理、声音处理、3D 运算等就非常方便了,不会产生性能负担。

如果要直接转移数据的控制权,就要使用下面的写法。

// Transferable Objects 格式
worker.postMessage(arrayBuffer, [arrayBuffer]);// 例子
var ab = new ArrayBuffer(1);
worker.postMessage(ab, [ab]);

API

浏览器原生提供Worker()构造函数,用来供主线程生成 Worker 线程。

var myWorker = new Worker(jsUrl, options);

Worker()构造函数,可以接受两个参数。第一个参数是脚本的网址(必须遵守同源政策),该参数是必需的,且只能加载 JS 脚本,否则会报错。第二个参数是配置对象,该对象可选。它的一个作用就是指定 Worker 的名称,用来区分多个 Worker 线程。

// 主线程
var myWorker = new Worker('worker.js', { name : 'myWorker' });// Worker 线程
self.name // myWorker

Worker()构造函数返回一个 Worker 线程对象,用来供主线程操作 Worker。Worker 线程对象的属性和方法如下。

  • Worker.onerror:指定 error 事件的监听函数。
  • Worker.onmessage:指定 message 事件的监听函数,发送过来的数据在Event.data属性中。
  • Worker.onmessageerror:指定 messageerror 事件的监听函数。发送的数据无法序列化成字符串时,会触发这个事件。
  • Worker.postMessage():向 Worker 线程发送消息。
  • Worker.terminate():立即终止 Worker 线程。

Web Worker 有自己的全局对象,不是主线程的window,而是一个专门为 Worker 定制的全局对象。因此定义在window上面的对象和方法不是全部都可以使用。

Worker 线程有一些自己的全局属性和方法。

  • self.name: Worker 的名字。该属性只读,由构造函数指定。
  • self.onmessage:指定message事件的监听函数。
  • self.onmessageerror:指定 messageerror 事件的监听函数。发送的数据无法序列化成字符串时,会触发这个事件。
  • self.close():关闭 Worker 线程。
  • self.postMessage():向产生这个 Worker 线程发送消息。
  • self.importScripts():加载 JS 脚本。

Javascript_ES5_学习笔记相关推荐

  1. PyTorch 学习笔记(六):PyTorch hook 和关于 PyTorch backward 过程的理解 call

    您的位置 首页 PyTorch 学习笔记系列 PyTorch 学习笔记(六):PyTorch hook 和关于 PyTorch backward 过程的理解 发布: 2017年8月4日 7,195阅读 ...

  2. 容器云原生DevOps学习笔记——第三期:从零搭建CI/CD系统标准化交付流程

    暑期实习期间,所在的技术中台-效能研发团队规划设计并结合公司开源协同实现符合DevOps理念的研发工具平台,实现研发过程自动化.标准化: 实习期间对DevOps的理解一直懵懵懂懂,最近观看了阿里专家带 ...

  3. 容器云原生DevOps学习笔记——第二期:如何快速高质量的应用容器化迁移

    暑期实习期间,所在的技术中台-效能研发团队规划设计并结合公司开源协同实现符合DevOps理念的研发工具平台,实现研发过程自动化.标准化: 实习期间对DevOps的理解一直懵懵懂懂,最近观看了阿里专家带 ...

  4. 2020年Yann Lecun深度学习笔记(下)

    2020年Yann Lecun深度学习笔记(下)

  5. 2020年Yann Lecun深度学习笔记(上)

    2020年Yann Lecun深度学习笔记(上)

  6. 知识图谱学习笔记(1)

    知识图谱学习笔记第一部分,包含RDF介绍,以及Jena RDF API使用 知识图谱的基石:RDF RDF(Resource Description Framework),即资源描述框架,其本质是一个 ...

  7. 计算机基础知识第十讲,计算机文化基础(第十讲)学习笔记

    计算机文化基础(第十讲)学习笔记 采样和量化PictureElement Pixel(像素)(链接: 采样的实质就是要用多少点(这个点我们叫像素)来描述一张图像,比如,一幅420x570的图像,就表示 ...

  8. Go 学习推荐 —(Go by example 中文版、Go 构建 Web 应用、Go 学习笔记、Golang常见错误、Go 语言四十二章经、Go 语言高级编程)

    Go by example 中文版 Go 构建 Web 应用 Go 学习笔记:无痕 Go 标准库中文文档 Golang开发新手常犯的50个错误 50 Shades of Go: Traps, Gotc ...

  9. MongoDB学习笔记(入门)

    MongoDB学习笔记(入门) 一.文档的注意事项: 1.  键值对是有序的,如:{ "name" : "stephen", "genda" ...

最新文章

  1. 院士论坛|李德仁:测绘遥感与智能驾驶
  2. telerik 某些ajax拿数据方式下 load on demand 不起作用
  3. 部署LVS-DR(LVS+Keepalived)群集
  4. Java——Socket通信原理
  5. python最好用的助手_推荐5款好用的Python工具
  6. IM、RTC技术两生花,看融云如何打造“IM+RTC+Push”一站式通信云服务
  7. raspberry pi_如何在Raspberry Pi上使用LÖVE游戏引擎对游戏进行编程
  8. vscode代码编辑框控件_vscode编辑器的使用及插件
  9. 图像加密标准测试图库
  10. 计算机考研专业课408什么意思,考研408是什么意思
  11. python绘制各种摆线(包括心形线星形线等,超炫酷)
  12. 关于vivo手机拍照后无法跳转裁剪
  13. USB设备被识别流程
  14. android 实现刮刮乐刮奖效果
  15. 手把手教你做出数据可视化项目(七)可视化图表数据动态获取及界面跳转
  16. oracle中clob类型的使用
  17. U-Net卷积神经网络
  18. 参考虚幻引擎UObjectBase类源代码定义出 UObject对象成员的偏移量
  19. Linux安装PHP(最新版)
  20. 剑指offer:顺时针打印矩阵(Python)

热门文章

  1. RestTemplate设置通用header 并获取header请求参数
  2. 2020/12/17 ubuntu16.04 NTP时间同步配置
  3. 怎样成为一名优秀的科学家
  4. 射频器件的基本参数1
  5. 云南师范大学计算机基础教学,云南师范大学841信息技术基础(含计算机文化基础、多媒体技术基础)考研复习经验...
  6. 痞子衡嵌入式:串口调试工具Jays-PyCOM诞生记(3)- 串口功能实现(pySerial)
  7. 2019美团点评校招笔试劝退之旅
  8. 图形处理之 OpenGL
  9. python实现单机斗地主手机版下载_单机斗地主手机版下载免费版乐
  10. Xcode 11的问题及 Xcode 11 beta 1和beta 2 版下载链接, 官方下载后上传到百度网盘的.