WebKit
Gecko
WebKit
💥Trident
Blink
用户界面
。包括地址栏、前进/后退按钮、书签菜单等。除了浏览器主窗口显示的您请求的页面外,其余显示的各个部分都属于用户界面。javascript
浏览器引擎
。在用户界面和呈现引擎之间传送指令。html
渲染引擎
。负责显示请求的内容。若是请求的内容是 HTML,它就负责解析 HTML 和 CSS 内容,并将解析后的内容显示在屏幕上。前端
网络层
。用于网络调用,好比 HTTP 请求。其接口与平台无关,并为全部平台提供底层实现。java
用户界面后端
。用于绘制基本的窗口小部件,好比组合框和窗口。其公开了与平台无关的通用接口,而在底层使用操做系统的用户界面方法。node
JavaScript 解释器。
用于解析和执行 JavaScript 代码。git
数据存储。
这是持久层。浏览器须要在硬盘上保存各类数据,例如 Cookie。新的 HTML 规范 (HTML5) 定义了“网络数据库”,这是一个完整(可是轻便)的浏览器内数据库。程序员
::: tip NOTICEgithub
Cookie localStorage Session IndexDb :::web
一般一个浏览器会至少存在三个线程:JS引擎线程(用于处理JS)
、GUI渲染线程(用于页面渲染)
、浏览器时间触发线程(用于控制交互)
。 而由于JS能够操做DOM元素,进而会影响到GUI的渲染结果,所以JS引擎线程与GUI渲染线程是互斥的。也就是说当JS引擎线程处于运行状态时,GUI渲染线程将处于冻结状态。 JS引擎是基于事件驱动,采用的是单线程运行机制。即JS引擎会只会顺序的从任务列表中取任务,并执行。面试
(1)UI线程 GUI渲染线程(渲染页面)
(2)JS线程 JS引擎线程(处理脚本)
(3)事件(触发)线程 事件触发线程(控制交互)
::: tip NOTICE
包括浏览器有时候会开辟新的线程好比用完就扔的Http请求线程(Ajax)等等其余线程 它们共同构成了浏览器的UI线程 这些线程在UI线程的控制下井井有理的工做着 关于这个常驻线程网上观点不一致,各个浏览器实现可能也不同,这里就不深考究了 虽然我把js引擎线程放在了右下角,可是它是浏览器的主线程 并且它和GUI渲染线程
是水火不容的,不可以同时工做 道理很简单,由于它们都要影响DOM,若是 js 线程想要某个DOM的样式,渲染引擎必须中止工做
:::
🐯🙃🏃🎉⚠️
::: tip NOTICE 其中 SetTimeout :在指定的毫秒数后调用指定的代码段;SetInternal:在指定的时间间隔内(ms)循环调用指定的代码段。这两个函数内都涉及到时间计数器,也就是都涉及到一个相似与MFC定时器。JS引擎自己就只能单线程运行,所以定时器须要由其余的外部线程来启动。因此对JS引擎而言,定时器线程能够被视为异步线程。但当定时器时间到达后,所触发的事件则必须在任务列表中排队,等候JS引擎的处理。 :::
关于setTimeout下面有一个例子,能够帮助深刻理解:
setTimeout(function () { while (true) { } }, 1000);
setTimeout(function () { alert('end 2'); }, 2000);
setTimeout(function () { alert('end 1'); }, 100);
alert('end');
复制代码
执行的结果是弹出‘end’‘end 1’,而后浏览器假死,就是不弹出‘end 2’。也就是说第一个settimeout里执行的时候是一个死循环,这个直接致使了理论上比它晚一秒执行的第二个settimeout里的函数被阻塞,这个和咱们平时所理解的异步函数多线程互不干扰是不符的。
为何JavaScript是单线程 单线程就是同一时间只能干一件事
那么JavaScript多线程很差吗,那效率多高啊 ? 很差
::: tip NOTICE
js设计出来就是为了与用户交互,处理DOM 若是JavaScript多线程了,那就必须处理多线程同步的问题
(依稀记得曾经被C++线程同步支配的恐怖 假如js是多线程,同一时间一个线程想要修改DOM,另外一个线程想要删除DOM 问题就变得复杂许多,浏览器不知道听谁的,若是引入“锁”的机制,那就麻烦死了(那我就不学前端了( ̄_, ̄ )
这样一个脚本语言
根本没有必要搞得那么复杂,因此JavaScript诞生起就是单线程执行
:::
虽然H5提出了Web Worker
,可是它不可以操做DOM
,仍是要委托给大哥js主线程解决
这些子线程彻底受主线程老大控制的(并且受不少限制),实际上并无改变JavaScript单线程的本质
咱们先来看看什么是执行栈 栈是先进后出(FILO)的数据结构
执行栈
中存放正在执行的任务,每个任务叫作“帧”
举个例子
function foo(c){
var a = 1;
bar(200);
}
function bar(d){
var b = 2;
}
foo(100);
复制代码
咱们来看看执行栈发生了怎样的变化
执行栈为空栈
执行栈其实至关于js主线程(同步顺序执行)
队列是先入先出(FIFO)的数据结构
js线程中还存在着一个任务队列
任务队列包含了一系列待处理的任务 单线程就意味着全部任务须要一个接一个的执行,若是一个任务执行的时间太长,那后面的任务就不得不等着
::: tip NOTICE
就比如护士阿姨给排队的小朋友打针,若是最前面的小朋友一直滚针,那就一直扎,后面的小朋友就得等着(这比喻好像不恰当) 但是若是最前面的小朋友晕针昏倒了 那么护士阿姨不可能坐那里了等到他醒来,应该先给后面的小朋友扎针 也就是至关于把那位小朋友“挂起”(异步) :::
因此,任务能够分为两种
::: tip NOTICE
同步任务就是正在主线程执行栈中执行的任务(在屋子内打针的小朋友)
而异步任务是在任务队列等候处理的任务(在屋子外等候打针的小朋友)
一旦执行栈中没有任务了,它就会从执行队列
中获取任务执行 :::
任务队列是一个事件的队列
,IO设备(输入/输出设备)每完成一项任务,就会在任务队列中添加事件处理
用户触发了事件
,也一样会将回调添加到任务队列中去
主线程执行异步任务
,即是执行回调函数(事件处理函数)
只要执行栈一空,排在执行队列前面的处理函数便会被优先读取执行 不过主线程会检查时间,某些事件须要到了规定时间才能进入主线程处理(定时器事件)
Event Loop
主线程从执行队列不断地获取任务,这个过程是循环不断地,叫作“Event Loop”事件循环 同步任务老是会在异步任务以前执行 只有当前的脚本执行完,才可以去拿任务队列中的任务执行 前面也说到了,任务队列中的事件能够是定时器事件 定时器分为两种 setTimeout() 和 setInterval() 前者是定时执行一次,后者定时重复执行 第一个参数为执行的回调函数,第二个参数为间隔时间(ms)
来看这样一个例子
setTimeout(function(){
console.log('timer');
}, 1000);
console.log(1);
console.log(2);
console.log(3);
复制代码
这个没什么问题,浏览器打印的是
1 2 3 timer
复制代码
可是这样呢
setTimeout(function(){
console.log('timer');
}, 0);//0延时
console.log(1);
console.log(2);
console.log(3);
复制代码
浏览器打印依然打印的是 1 2 3 timer
也许有同窗知道,旧版浏览器,setTimeout定时至少是10ms(即使你设置了0ms), H5新规范是定时至少4ms
,改变DOM也是至少16ms
咱们能够暂且认为是这个缘由 那么我再改动一下代码
setTimeout(function(){
console.log('timer');
}, 0);
var a = +new Date();
for(var i = 0; i < 1e5; i++){
console.log(1);
}
var b = +new Date();
console.log(b - a);
复制代码
这回够刺激了吧,输出10w次,我浏览器都假死了(心疼我chrome) 不只如此,我还打印了循环所用时间 来看看控制台
输出了10w个1,用了将近7s timer依然在最后打印 这就证实了我前面说的话: 同步任务老是会在异步任务以前执行 只有我执行栈空了,才会去你任务队列中取任务执行
实例 最后我举一个例子加深一下理解
demo.onclick = function(){
console.log('click');
}
function foo(a){
var b = 1;
bar(200);
}
function bar(c){
var d = 2;
click//伪代码 此时触发了click事件(这里我伪装程序运行到这里手动点击了demo)
setTimeout(function(){
console.log('timer');
}, 0);
}
foo(100);
复制代码
怕你们蒙我就不写Ajax了 Ajax若是处理结束后(经过Http请求线程),也会将回调函数放在任务队列中 还有一点 click 那一行伪代码我最开始是想用demo.click()模拟触发事件 后来在测试过程当中,发现它好像跟真实触发事件不太同样 它应该是不经过触发事件线程,而是存在于执行栈中,就至关于单纯地执行click回调函数 不过这只是我本身的想法有待考证,不过这不是重点,重点是咱们理解这个过程,请你们不要吐槽我╰( ̄▽ ̄)╭
下面看看执行这段代码时发生了什么(主要说栈和队列的问题,不会赘述预编译过程)
这一次,完全弄懂 JavaScript 执行机制 本文的目的就是要保证你完全弄懂javascript的执行机制,若是读完本文还不懂,能够揍我。
不论你是javascript新手仍是老鸟,不管是面试求职,仍是平常开发工做,咱们常常会遇到这样的状况:给定的几行代码,咱们须要知道其输出内容和顺序。由于javascript是一门单线程语言,因此咱们能够得出结论:
javascript是按照语句出现的顺序执行的 看到这里读者要打人了:我难道不知道js是一行一行执行的?还用你说?稍安勿躁,正由于js是一行一行执行的,因此咱们觉得js都是这样的:
let a = '1';
console.log(a);
let b = '2';
console.log(b);
复制代码
然而实际上js是这样的:
setTimeout(function(){
console.log('定时器开始啦')
});
new Promise(function(resolve){
console.log('立刻执行for循环啦');
for(var i = 0; i < 10000; i++){
i == 99 && resolve();
}
}).then(function(){
console.log('执行then函数啦')
});
console.log('代码执行结束');
复制代码
依照js是按照语句出现的顺序
执行这个理念,我自信的写下输出结果:
//"定时器开始啦"
//"立刻执行for循环啦"
//"执行then函数啦"
//"代码执行结束"
复制代码
去chrome上验证下,结果彻底不对,瞬间懵了,说好的一行一行执行的呢?
咱们真的要完全弄明白javascript的执行机制了。
::: tip NOTICE
宏任务 --> 微任务 --> 宏任务 --> 微任务 -->[]
:::
总体代码script,setTimeout,setInterval, new Promise
Promise.then,process.nextTick
主要代码结构就是宏任务
以后执行微任务
javascript是一门单线程语言,在最新的HTML5中提出了Web-Worker,但javascript是单线程这一核心仍未改变。因此一切javascript版的"多线程"都是用单线程模拟出来的,一切javascript多线程都是纸老虎!
既然js是单线程,那就像只有一个窗口的银行,客户须要排队一个一个办理业务,同理js任务也要一个一个顺序执行。若是一个任务耗时过长,那么后一个任务也必须等着。那么问题来了,假如咱们想浏览新闻,可是新闻包含的超清图片加载很慢,难道咱们的网页要一直卡着直到图片彻底显示出来?所以聪明的程序员将任务分为两类:
当咱们打开网站时,网页的渲染过程就是一大堆同步任务,好比页面骨架和页面元素的渲染。而像加载图片音乐之类占用资源大耗时久的任务,就是异步任务。关于这部分有严格的文字定义,但本文的目的是用最小的学习成本完全弄懂执行机制,因此咱们用导图来讲明:
导图要表达的内容用文字来表述的话:
咱们不由要问了,那怎么知道主线程执行栈为空啊?js 引擎存在 monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。
说了这么多文字,不如直接一段代码更直白:
let data = [];
$.ajax({
url:www.javascript.com,
data:data,
success:() => {
console.log('发送成功!');
}
})
console.log('代码执行结束');
复制代码
上面是一段简易的ajax请求代码:
ajax
进入Event Table,注册回调函数success
。相信经过上面的文字和代码,你已经对js的执行顺序有了初步了解。接下来咱们来研究进阶话题:setTimeout。
大名鼎鼎的setTimeout无需再多言,你们对他的第一印象就是异步能够延时执行,咱们常常这么实现延时3秒执行:
setTimeout(() => {
console.log('延时3秒');
},3000)
复制代码
渐渐的setTimeout用的地方多了,问题也出现了,有时候明明写的延时3秒,实际却5,6秒才执行函数,这又咋回事啊?
先看一个例子:
setTimeout(() => {
task();
},3000)
console.log('执行console');
复制代码
//执行console
//task()
复制代码
setTimeout(() => {
task()
},3000)
sleep(10000000)
复制代码
乍一看其实差很少嘛,但咱们把这段代码在chrome执行一下,却发现控制台执行task()须要的时间远远超过3秒,说好的延时三秒,为啥如今须要这么长时间啊?
这时候咱们须要从新理解setTimeout的定义。咱们先说上述代码是怎么执行的:
Important
task()
进入Event Table
并注册,计时开始。sleep
函数,很慢,很是慢,计时仍在继续。timeout
完成,task()进入Event Queue
,可是sleep也太慢了吧,还没执行完,只好等着。
sleep终于执行完
了,task()
终于从Event Queue进入了主线程执行。上述的流程走完,咱们知道setTimeout
这个函数,是通过指定时间后,把要执行的任务(本例中为task())加入到Event Queue中,又由于是单线程任务要一个一个执行,若是前面的任务须要的时间过久,那么只能等着,致使真正的延迟时间远远大于3秒
。
咱们还常常遇到setTimeout(fn,0)
这样的代码,0秒后执行又是什么意思呢?是否是能够当即执行呢?
答案是不会的,setTimeout(fn,0)的含义是,指定某个任务在主线程最先可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务所有执行完成,
栈为空就立刻执行。举例说明:
//代码1
console.log('先执行这里');
setTimeout(() => {
console.log('执行啦')
},0);
//代码2
console.log('先执行这里');
setTimeout(() => {
console.log('执行啦')
},3000);
复制代码
代码1的输出结果是:
//先执行这里
//执行啦
复制代码
代码2的输出结果是:
//先执行这里
// ... 3s later
// 执行啦
复制代码
关于setTimeout要补充的是,即使主线程为空,0毫秒实际上也是达不到的。根据HTML的标准,最低是4毫秒
。有兴趣的同窗能够自行了解。
上面说完了setTimeout,固然不能错过它的孪生兄弟setInterval。他俩差很少,只不事后者是循环的执行。 对于执行顺序来讲,setInterval会每隔指定的时间将注册的函数置入 Event Queue
,若是前面的任务耗时过久,那么一样须要等待。
惟一须要注意的一点是,对于setInterval(fn,ms)
来讲,咱们已经知道不是每过 ms秒会执行一次 fn,而是每过 ms秒,会有 fn进入 Event Queue
。一旦setInterval的回调函数fn
执行时间超过了延迟时间ms
,那么就彻底看不出来有时间间隔了
。这句话请读者仔细品味。
让咱们想象一个意外状况,好比说下面的setInterval
setInterval(function () {
func(i++);
}, 100)
复制代码
咱们每100毫秒调用一次func函数,若是func的执行时间少于100毫秒的话,在遇到下一个100毫秒前就可以执行完:
但若是func的执行时间大于100毫秒,该触发下一个func函数时以前的尚未执行完怎么办?答案以下图所示,那么第二个func会在队列(这里的队列是指event loop)中等待,直到第一个函数执行完
若是第一个函数的执行时间特别长,在执行的过程当中本应触发了许多个func怎么办,那么全部这些应该触发的函数都会进入队列吗?
不,只要发现队列中有一个被执行的函数存在,那么其余的通通忽略。以下图,在第300毫秒和400毫秒处的回调都被抛弃,一旦第一个函数执行完后,接着执行队列中的第二个,即便这个函数已经“过期”好久了。
还有一点,虽然你在setInterval的里指定的周期是100毫秒,但它并不能保证两个函数之间调用的间隔必定是一百毫秒。在上面的状况中,若是队列中的第二个函数时在第450毫秒处结束的话,在第500毫秒时,它会继续执行下一轮func,也就是说这之间的间隔只有50毫秒,而非周期100毫秒
传统的定时器咱们已经研究过了,接着咱们探究 Promise与 process.nextTick(callback)的表现。
Promise的定义和功能本文再也不赘述,不了解的读者能够学习一下阮一峰老师的Promise。而process.nextTick(callback)相似node.js版的"setTimeout",在事件循环的下一次循环中调用 callback 回调函数。
咱们进入正题,除了广义的同步任务和异步任务,咱们对任务有更精细的定义:
new Promise
不一样类型的任务会进入对应的Event Queue
,好比setTimeout
和setInterval
会进入相同的Event Queue。
事件循环的顺序,决定js代码的执行顺序。进入总体代码(宏任务)后,开始第一次循环。接着执行全部的微任务。而后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行全部的微任务。听起来有点绕,咱们用文章最开始的一段代码说明:
setTimeout(function() {
console.log('setTimeout');
})
new Promise(function(resolve) {
console.log('promise');
}).then(function() {
console.log('then');
})
console.log('console');
复制代码
Event Queue
中setTimeout对应的回调函数,当即执行。事件循环,宏任务,微任务的关系如图所示:
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
复制代码
Event Queue
中。咱们记为then1。process1和then1两个微任务
。好了,第一轮事件循环正式结束,这一轮的结果是输出1,7,6,8。那么第二轮时间循环从setTimeout1宏任务开始:
宏任务Event Queue | 微任务Event Queue |
---|---|
setTimeout2 | process2 |
setTimeout2 | then1 |
宏任务Event Queue | 微任务Event Queue |
---|---|
process3 | |
then3 |
整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。
(请注意,node环境下的事件监听依赖 libuv与前端环境不彻底相同,输出顺序可能会有偏差)
(1)js的异步 咱们从最开头就说javascript是一门单线程
语言,无论是什么新框架新语法糖实现的所谓异步,其实都是用同步的方法去模拟的,紧紧把握住单线程这点很是重要
。
(2)事件循环Event Loop 事件循环是js实现异步的一种方法,也是js的执行机制。
(3)javascript的执行和运行 执行和运行有很大的区别,javascript在不一样的环境下,好比node,浏览器,Ringo等等,执行方式是不一样的。而运行大多指javascript解析引擎,是统一的。
(4)setImmediate 微任务和宏任务还有不少种类,好比setImmediate等等,执行都是有共同点的,有兴趣的同窗能够自行了解。
javascript是一门单线程语言
Event Loop 是 javascript 的执行机制
紧紧把握两个基本点,以认真学习javascript为中心,早日成为像我同样的大牛
皮一下, 很开心
Cookie 是小甜饼的意思。顾名思义,cookie 确实很是小,它的大小限制为4KB
左右,是网景公司的前雇员 Lou Montulli 在1993年3月的发明。它的主要用途有保存登陆信息
,好比你登陆某个网站市场能够看到“记住密码”,这一般就是经过在 Cookie 中存入一段辨别用户身份的数据来实现的。
localStorage 是 HTML5 标准中新加入的技术,它并非什么划时代的新东西。早在 IE 6 时代,就有一个叫 userData 的东西用于本地存储,而当时考虑到浏览器兼容性,更通用的方案是使用 Flash。而现在,localStorage 被大多数浏览器所支持,若是你的网站须要支持 IE6+,那以 userData 做为你的 polyfill 的方案是种不错的选择。
sessionStorage 与 localStorage 的接口相似,但保存数据的生命周期与 localStorage 不一样
。作事后端开发的同窗应该知道 Session 这个词的意思,直译过来是“会话
”。而 sessionStorage 是一个前端的概念,它只是能够将一部分数据在当前会话中保存下来,刷新页面数据依旧存在
。但当页面关闭后,sessionStorage 中的数据就会被清空。
特性 | Cookie | localStorage | sessionStorage |
---|---|---|---|
数据的生命期 | 通常由服务器生成,可设置失效时间。若是在浏览器端生成Cookie,默认是关闭浏览器后失效 | 除非被清除,不然永久保存 | 仅在当前会话下有效,关闭页面或浏览器后被清除 |
存放数据大小 | 4K左右 | 通常为5MB | 通常为5MB |
与服务器端通讯 | 每次都会携带在HTTP头中,若是使用cookie保存过多数据会带来性能问题 | 仅在客户端(即浏览器)中保存,不参与和服务器的通讯 | 仅在客户端(即浏览器)中保存,不参与和服务器的通讯 |
易用性 | 须要程序员本身封装,源生的Cookie接口不友好 | 源生接口能够接受,亦可再次封装来对 Object和 Array有更好的支持 | 源生接口能够接受,亦可再次封装来对 Object和 Array有更好的支持 |
做用域大小 | 同域 | 同域 | 同域 |
数据结构 | key-value(字符创-字符串) | key-value | key-value |
有了对上面这些差异的直观理解,咱们就能够讨论三者的应用场景了。
由于考虑到每一个 HTTP 请求都会带着 Cookie 的信息,因此 Cookie 固然是能精简就精简啦,比较经常使用的一个应用场景就是判断用户是否登陆。针对登陆过的用户,服务器端会在他登陆时往 Cookie 中插入一段加密过的惟一辨识单一用户的辨识码,下次只要读取这个值就能够判断当前用户是否登陆啦。曾经还使用 Cookie 来保存用户在电商网站的购物车信息,现在有了 localStorage,彷佛在这个方面也能够给 Cookie 放个假了~
而另外一方面 localStorage 接替了 Cookie 管理购物车的工做,同时也能胜任其余一些工做。好比HTML5游戏一般会产生一些本地数据,localStorage 也是很是适用的。若是遇到一些内容特别多的表单,为了优化用户体验,咱们可能要把表单页面拆分红多个子页面,而后按步骤引导用户填写。这时候 sessionStorage 的做用就发挥出来了。
localStorage 和 sessionStorage 有着统一的API接口,这为两者的操做提供了极大的便利。下面以 localStorage 为例来介绍一下 API 接口使用方法,一样这些接口也适用sessionStorage
添加键值对:localStorage.setItem(key, value)
setItem 用于把值 value 存储到键key上,除了使用 setItem ,还可使用 localStorage.key = value
或者 localStorage['key'] = value
这两种形式。 另外须要注意的是,key和value值必须是字符串形式的,若是不是字符串,会调用它们相应的toString() 方法来转换成字符串再存储。 当咱们要存储对象是,应先转换成咱们可识别的字符串格式(好比JSON格式)再进行存储。
// 把一个用户名 ( lilei )存储到 name 的键上
localStorage.setItem('name', 'lilei');
// localStorage.name = 'lilei';
// localStorage['name'] = 'lilei';
// 把一个用户存储到user的键上
localStorage.setItem('user', JSON.stringify({id:1, name:'lilei'}));
复制代码
获取键值:localStorage.getItem(key)
getItem 用于获取键 key 对应的数据,和 setItem 同样,getItem 也有两种等效形式 value = localStorage.key和 value = localStorage['key']
。 获取到的 value 值是字符串类型,若是须要其余类型,要作手动的类型转换。
// 获取存储到 name 的键上的值
var name = localStorage.getItem('name');
// var name = localStorage.name;
// var name = localStorage['name'];
// 获取存储到user的键上的值
var user = JSON.parse(localStorage.getItem('user'));
复制代码
删除键值对:localStorage.removeItem(key)
removeItem 用于删除指定键的项, localStorage 没有数据过时
的概念,全部数据若是失效了,须要开发者手动删除。
var name = localStorage.getItem('name'); // 'lilei'
// 删除存储到 name 的键上的值
localStorage.removeItem('name');
name = localStorage.getItem('name'); // null
复制代码
清除全部键值对:localStorage.clear()clear 用于删除全部存储的内容, 它和 removeItem不一样的地方是 removeItem 删除的是某一项,而clear是删除全部。
// 清除 localStorage
localStorage.clear();
var len = localStorage.length; // 0
复制代码
获取 localStorage 的属性名称(键名称):localStorage.key(index)key 方法用于获取指定索引的键名称。须要注意的是赋值早的键值对应的索引值大,赋值晚的键值对应的索引小,
key方法可用于遍历 localStorage 存储的键值。
localStorage.setItem('name','lilei');
var key = localStorage.key(0); // 'name'
localStorage.setItem('age', 10);
key = localStorage.key(0); // 'age'
key = localStorage.key(1); // 'name'
复制代码
获取 localStorage 中保存的键值对的数量:localStorage.length length 属性用于获取 localStorage 中键值对的数量。
localStorage.setItem('name','lilei');
var len = localStorage.len; // 1
localStorage.setItem('age', 10);
len = localStorage.len; // 2
复制代码
storage 事件当存储的数据发生变化时,会触发 storage 事件
。 但要注意的是它不一样于click类的事件会事件捕获和冒泡
,storage 事件更像是一个通知,不可取消。
触发这个事件会调用同域
下其余窗口的storage事件,不过触发storage的窗口(即当前窗口)不触发这个事件(chrome 好像能够❤️)
🚥(可用于多页面通讯)
storage 的 event
对象的经常使用属性以下:
oldValue:更新前的值。若是该键为新增长,则这个属性为null。
newValue:更新后的值。若是该键被删除,则这个属性为null。
url:原始触发storage事件的那个网页的网址。
key:存储 store的 key名
复制代码
function storageChanged(/*event */) {
console.log(arguments);
}
window.addEventListener('storage', storageChanged, false);
复制代码
IndexedDB 能够存储很是多的数据,好比 Object,files,blobs 等,里面的存储结构是根据 Database 来进行存储的。每一个 DB 里面能够有不一样的 object stores。
概述
1.浏览器查找域名的 IP 地址
2.这一步包括 DNS 具体的查找过程,包括:浏览器缓存->系统缓存->路由器缓存>IP 根服务器>找到指定Ip
3.浏览器向 web 服务器发送一个 HTTP 请求
4.服务器的永久重定向响应(从 http://example.com 到 http://www.example.com)
5.浏览器跟踪重定向地址
6.服务器处理请求
7.服务器返回一个 HTTP 响应
8.浏览器显示 HTML
9.浏览器发送请求获取嵌入在 HTML 中的资源(如图片、音频、视频、CSS、JS等等)
10.浏览器发送异步请求
复制代码
HTTP 请求返回的 HTML 传递到浏览器后,若是有 gzip 会先解压,而后接下来最重要的问题是要知道它的编码是什么,好比一样一个「中」字,在 UTF-8 编码下它的内容实际上是「11100100 10111000 10101101」也就是「E4 B8 AD」,而在 GBK 下则是「11010110 11010000」,也就是「D6 D0」,如何才能知道文件的编码?能够有不少判断方法:
<meta>
中的 charset 属性值若是在这些地方都没指明,浏览器就很难处理,在它看来就是一堆「0」和「1」,好比「中文」,它在 UTF-8 下有 6 个字节,若是按照 GBK 能够当成「涓枃」这 3 个汉字来解释,浏览器怎么知道究竟是「中文」仍是「涓枃」呢?
不过正常人一眼就能认出「涓枃」是错的,由于这 3 个字太不常见了,因此有人就想到经过判断常见字的方法来检测编码,典型的好比 Mozilla 的 UniversalCharsetDetection,不过这东东误判率也很高,因此仍是指明编码的好。
这样后续对文本的操做就是基于「字符」(Character)的了,一个汉字就是一个字符,不用再关心它到底是 2 个字节仍是 3 个字节。
BOM 为浏览器窗口对象的一组 API。
用于操做浏览器的API
⚠️经过window 或者省略window 或者document 或者 event 鼠标事件
BOM是 browser object model的缩写,简称浏览器对象模型
BOM提供了独立于内容而与浏览器窗口进行交互的对象
因为BOM主要用于管理窗口与窗口之间的通信,所以其核心对象是window
BOM由一系列相关的对象构成,而且每一个对象都提供了不少方法与属性
BOM缺少标准,JavaScript语法的标准化组织是 ECMA,DOM的标准化组织是 W3C
BOM最初是 Netscape 浏览器标准的一部分
复制代码
属性
navigator
location
方法
history
方法
screen
Window 方法
Window 事件
复制代码
window.onload 和 document.onload
document.onload 是dom 解析完成就会触发
window.onload
复制代码
1. window.history
操纵浏览器记录
history.back(); // 等同于点击浏览器的回退按钮
history.go(-1); //等同于history.back()
2.🐯 window.innerHeight/innerWidth vs window.outerWidth/outerHeight
浏览器窗口的视口(viewport)高宽 vs 获取完整窗口大小:
3. 🐯window.location
操做刷新按钮和地址栏
设置或取得当前 URL 的主机名称和端口
`http://www.baidu.com:80/qs/ans` // http://www.baidu.com:80
5. location.pathname [string]
设置或取得当前 URL 的路径部分
🐯6. location.search [string]
查询或设置当前URL的?号开始的字符串
🐯7.location.href
返回整个URL
8. location.hash
返回或设置#号开始的部分
9. location.origin
返回当前域名
10. window.navigator
只读对象
11. window.screen
返回有关屏幕大小宽高以及颜色深度等信息
screen.availWidth | 可用的屏幕宽度
screen.availHeight | 可用的屏幕宽度
screen.width | 当前屏幕宽度 (分辨率)
screen.height | 当前屏幕高度 (分辨率值)
screen.colorDepth | 当前屏幕色彩深度(bit)
12. navigator.userAgent
得到HTTP请求的用户带头的值
13. window.self
即window 尽可能少使用全局变量!!
14. window.top
返回最顶层页面
15. window.resizeTo(width,height)
resizeBy(width/height)增量
16. event 事件位置
参照屏幕左上角: e.screenX, e.screenY
参照文档显示区左上角: e.clientX||e.x , e.clientY||e.y
参照所在父元素的左上角: e.offsetX , e.offsetY
17. location.replace(“url”) 当前页面打开不可后退⚠️
location.replace vs history.replaceState⚠️
18. ⚠️从新加载当前页面: location.reload(false/true/'') location.reload()
document.location.reload() vs location.reload()
19.定时器一次性: clearInterval(timer);time=null
定时器周期性: clearTimeout(timer);time=null
20. ️[btn/window].addEventListener(“事件名”,函数对象[命名函数, 匿名函数没法取消会形成泄漏])
21. ️[btn/window].remove(“事件名”,函数对象[命名函数, 匿名函数没法取消会形成泄漏])
22. 阻止冒泡:
e.stopPropagation(); e.cancelBubble[ie⚠️]
23. 取消事件(阻止默认行为):
e.preventDefault
24. 建立cookie:
document.cookie="变量名=值;expires="+new Date().toGMTString();
复制代码
http://www.github.com:8080/index.html?user=li-xinyang&lang=zh-CN#home
| | | | | |
protocol | | | | |
hostname port | | |
\ / pathname search hash
host
4. location.host [string]
复制代码
方法 | 方法描述 |
---|---|
alert(), confirm() 返回真假, prompt() 返回用户输入值 | 三种对话框 |
setTimeout(), setInterval() | 计时器 |
open(), | close() |
::: tip 提示 NOTE:对话框会阻塞线程。
:::
打开或关闭窗口
var w = window.open('subwindow.html', 'subwin', 'width=300, height=300, status=yes, resizable=yes');
// 既可关闭窗口
w.close();
复制代码
方法 | 方法描述 |
---|---|
load | 文档和全部图片完成加载时 |
unload | 离开当前文档时 |
beforeunload | 和 unload 相似,可是它提供询问用户是否确认离开的机会 |
resize | 拖动改变浏览器窗口大小时 |
scroll | 拖动浏览器时 |
专用事件: window对象-事件
onload
事件:咱们须要在网页文档下载完毕(全部资源)
时执行的程序代码,须要放在onload事件处理程序中去编写。
onunload
事件:在网页文档卸载
完毕后,须要在网页关闭时执行
的程序代码,须要放在onunload事件处理程序中去编写。
onbeforeunload
事件:对于浏览器准备卸载网页文件时的事件,也就是onbeforeunload事件是发生在卸载网页文件以前的事件,给用户提供一个取消的机会。
样例:
<body onload="alert('欢迎')" onunload="alert('再见!')" onbeforeunload="window.event.returnValue='请当心'">
</body>
复制代码
效果:打开网页,弹出一个“欢迎”的对话框,点击右上角的叉叉,弹出是否关闭网页的对话框,选择“肯定”,网页关闭,弹出一个“再见”的对话框。
// 测试javascript语句的执行顺序:
<script>
alert("ok");
</script>
<body onload="alert('欢迎')" onunload="alert('再见!')"
onbeforeunload="window.event.returnValue='请当心'">
Hello
<script>
alert("ok2");
</script>
</body>
<script>
alert("ok3");
</script>
复制代码
这个例子说明了,解析器是一边读文档
,一边解析里面的内容
。而onload事件发生在浏览器将程序装载完才发生
的事件。 若是网页文档很是大,装载的时间很是长,为了缓解用户等待的焦急心情,能够在开始放一个script
语句块,显示"正在下载"
,而后在中间的script语句块中放一个定时器,显示下载进度
。在最后显示装在完毕的信息。
通用事件: 鼠标和按键
onclick事件
:它是当用户点击某个Html元素时产生的事件
。咱们在 form表单的元素中常用到 onclick事件。
onmousemove
事件:它是当鼠标在某个html元素上移动
的时候所产生的事件,这个事件伴随着鼠标的移动而不断的重复发生。若是咱们要随着鼠标的移动而不断显示出鼠标的新坐标位置,就应该处理这个事件。
onmouseover事件
:它是指鼠标刚从外面移进到一个html元素的边界
时,所产生的事件。除非鼠标又移出html元素且再移入,不然这个事件不会重复发生。
onmouseout事件
:它是当鼠标移出一个html元素的边界时
所产生的事件。
onmousedown事件
:当鼠标的任何按键按下去
的时候产生的事件。
onmouseup事件
:当鼠标的任何按键弹起去
的时候产生的事件。
onkeydown
:当键盘的任何按键按下
去的时候产生的事件。
onkeyup事件
:当键盘的任何按键弹起的时候
产生的事件。
onkeypress事件
:当用户按下一个数字或字母按键
所产生的事件。按某个键是指"按下弹起"。
TBD....
原文连接 劼哥stone ::: tip NOTICE 《JavaScript 闯关记》之垃圾回收和内存管理 JavaScript 具备自动垃圾收集机制(GC:Garbage Collection),也就是说,执行环境会负责管理代码执行过程当中使用的内存。而在 C 和 C++ 之类的语言中,开发人员的一项基本任务就是手工跟踪内存的使用状况,这是形成许多问题的一个根源。
在编写 JavaScript 程序时,开发人员不用再关心内存使用问题,所需内存的分配以及无用内存的回收彻底实现了自动管理。这种垃圾收集机制的原理其实很简单:找出那些再也不继续使用的变量,而后释放其占用的内存。
为此,垃圾收集器会按照固定的时间间隔(或代码执行中预约的收集时间),周期性地执行这一操做。
正由于垃圾回收器的存在,许多人认为 JavaScript 不用太关心内存管理的问题,但若是不了解 JavaScript 的内存管理机制,咱们一样很是容易成内存泄漏(内存没法被回收)的状况。 :::
// 1.对象
new Object();
new MyConstructor();
{ a: 4, b: 5 }
Object.create()
复制代码
// 2.数组
new Array()
[ 1, 2, 3, 4 ]
复制代码
// 3.字符串,JavaScript 的字符串和 .NET 同样,使用资源池和 copy on write 方式管理字符串。
new String("hello hyddd")
"<p>" + e.innerHTML + "</p>"
复制代码
// 4.函数
var x = function () { ... }
new Function(code)
复制代码
// 5.闭包
function outer(name) {
var x = name
return function inner() {
return ", " + name
}
}
复制代码
下面咱们来分析一下函数中局部变量的正常生命周期。
一般,很容易判断变量是否还有存在的必要,但并不是全部状况下都这么容易就能得出结论(例如:使用闭包的时)。垃圾收集器必须跟踪哪一个变量有用哪一个变量没用,对于再也不有用的变量打上标记,以备未来收回其占用的内存。用于标识无用变量的策略可能会因实现而异,但具体到浏览器中的实现,则一般有两个策略:标记清除 和 引用计数。
JavaScript 中最经常使用的垃圾收集方式是 标记清除(mark-and-sweep
)。当变量进入环境(例如,在函数中声明一个变量)时,就将这个变量标记为“进入环境”
。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,由于只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”
。
function test(){
var a = 10 ; // 被标记 ,进入环境
var b = 20 ; // 被标记 ,进入环境
}
test(); // 执行完毕 以后 a、b又被标离开环境,被回收
复制代码
垃圾回收器在运行的时候会给存储在内存中的全部变量都加上标记
(固然,可使用任何标记方式)。而后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记
(例如,闭包)。而在此以后再被加上标记的变量将被视为准备删除的变量,缘由是环境中的变量已经没法访问到这些变量了
。最后,垃圾回收器完成内存清除工做,销毁那些带标记的值并回收它们所占用的内存空间
。
这种方式的主要缺点就是若是某些对象被清理后,内存是不连续的,那么就算内存占用率不高,例如只有50%,可是因为内存空隙太多,后来的大对象甚至没法存储到内存之中。
通常的处理方式都是在垃圾回收后进行整理操做,这种方法也叫 标记整理,整理的过程就是将不连续的内存向一端复制,使不连续的内存连续起来。
目前,IE9+、Firefox、Opera、Chrome 和 Safari 的 JavaScript 实现使用的都是 标记清除 式的垃圾收集策略(或相似的策略),只不过垃圾收集的时间间隔互有不一样。
另外一种不太常见的垃圾收集策略叫作 引用计数(reference counting
)。引用计数的含义是跟踪记录每一个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。若是同一个值又被赋给另外一个变量,则该值的引用次数加1。相反,若是包含对这个值引用的变量又取得了另一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,则说明没有办法再访问这个值了,于是就能够将其占用的内存空间回收回来。
这样,当垃圾收集器下次再运行时,它就会释放那些引用次数为零的值所占用的内存。
function test(){
var a = {} ; // a的引用次数为0
var b = a ; // a的引用次数加1,为1
var c = a; // a的引用次数再加1,为2
var b = {}; // a的引用次数减1,为1
}
复制代码
早期不少浏览器使用引用计数策略,但很快它就遇到了一个严重的问题:循环引用。循环引用指的是对象 A 中包含一个指向对象 B 的指针,而对象 B 中也包含一个指向对象 A 的引用。请看下面这个例子:
function problem(){
var objectA = new Object();
var objectB = new Object();
objectA.someOtherObject = objectB;
objectB.anotherObject = objectA;
}
复制代码
在这个例子中,objectA 和 objectB 经过各自的属性相互引用;也就是说,这两个对象的引用次数都是2。在采用 标记清除 策略的实现中,因为函数执行以后,这两个对象都离开了做用域,所以这种相互引用不是个问题。但在采用 引用计数 策略的实现中,当函数执行完毕后,objectA 和 objectB 还将继续存在,由于它们的引用次数永远不会是0。假如这个函数被重复屡次调用,就会致使大量内存得不到回收。为此,新一代浏览器都放弃了引用计数方式,转而采用标记清除来实现其垃圾收集机制。但是,引用计数致使的麻烦并未就此终结。
咱们知道,IE 中有一部分对象并非原生 JavaScript 对象。例如,其 BOM 和 DOM 中的对象就是使用 C++ 以 COM(Component Object Model,组件对象模型)对象的形式实现的,而 COM 对象的垃圾收集机制采用的就是引用计数策略。所以,即便 IE 的 JavaScript 引擎是使用标记清除策略来实现的,但 JavaScript 访问的 COM 对象依然是基于引用计数策略的。换句话说,只要在 IE 中涉及 COM 对象,就会存在循环引用的问题。下面这个简单的例子,展现了使用 COM 对象致使的循环引用问题:
var element = document.getElementById("some_element");
var myObject = new Object();
myObject.element = element;
element.someObject = myObject;
复制代码
这个例子在一个 DOM 元素(element)与一个原生 JavaScript 对象(myObject)之间建立了循环引用。其中,变量 myObject 有一个名为 element 的属性指向 element 对象;而变量 element 也有一个属性名叫 someObject 回指 myObject。因为存在这个循环引用,即便将例子中的 DOM 从页面中移除,它也永远不会被回收。
为了不相似这样的循环引用问题,最好是在不使用它们的时候手工断开原生 JavaScript 对象与 DOM 元素之间的链接。例如,可使用下面的代码消除前面例子建立的循环引用:
myObject.element = null;
element.someObject = null
复制代码
将变量设置为 null 意味着切断变量与它此前引用的值之间的链接。当垃圾收集器下次运行时,就会删除这些值并回收它们占用的内存。
为了解决上述问题,IE9 把 BOM 和 DOM 对象都转换成了真正的 JavaScript 对象。这样,就避免了两种垃圾收集算法并存致使的问题,也消除了常见的内存泄漏现象。
IE6 的垃圾回收是根据内存分配量运行的,当环境中存在256个变量、4096个对象、64k的字符串任意一种状况的时候就会触发垃圾回收器工做,看起来很科学,不用按一段时间就调用一次,有时候会不必,这样按需调用不是很好吗?可是若是环境中就是有这么多变量等一直存在,如今脚本如此复杂,那么垃圾回收器会一直工做,这样浏览器就无法儿玩儿了。
微软在 IE7 中作了调整,触发条件再也不是固定的,而是动态修改的,初始值和 IE6 相同,若是垃圾回收器回收的内存分配量低于程序占用内存的15%,说明大部份内存不可被回收,设的垃圾回收触发条件过于敏感,这时候把临界条件翻倍,若是回收的内存高于85%,说明大部份内存早就该清理了,这时候则将各类临界值重置回默认值。这一看似简单的调整,极大地提高了 IE7 在运行包含大量 JavaScript 的页面时的性能。
使用具有垃圾收集机制的语言编写程序,开发人员通常没必要操心内存管理的问题。可是,JavaScript 在进行内存管理及垃圾收集时面临的问题仍是有点不同凡响。其中最主要的一个问题,就是分配给 Web 浏览器的可用内存数量一般要比分配给桌面应用程序的少。这样作的目的主要是出于安全方面的考虑,目的是防止运行 JavaScript 的网页耗尽所有系统内存而致使系统崩溃。内存限制问题不只会影响给变量分配内存,同时还会影响调用栈以及在一个线程中可以同时执行的语句数量。
所以,确保占用最少的内存可让页面得到更好的性能。而优化内存占用的最佳方式,就是为执行中的代码只保存必要的数据。一旦数据再也不有用,最好经过将其值设置为 null 来释放其引用——这个作法叫作 解除引用(dereferencing)。
这一作法适用于大多数全局变量和全局对象的属性。局部变量会在它们离开执行环境时自动被解除引用,以下面这个例子所示:
function createPerson(name){
var localPerson = new Object();
localPerson.name = name;
return localPerson;
}
var globalPerson = createPerson("Nicholas");
// 手工解除globalPerson的引用
globalPerson = null;
复制代码
因为局部变量 localPerson 在 createPerson() 函数执行完毕后就离开了其执行环境, 所以无需咱们显式地去为它解除引用。可是对于全局变量 globalPerson 而言,则须要咱们在不使用它的时候手工为它解除引用, 这也正是上面例子中最后一行代码的目的。
不过,解除一个值的引用并不意味着自动回收该值所占用的内存。解除引用的真正做用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收。
和其余语言同样,JavaScript 的垃圾回收策略也没法避免一个问题:垃圾回收时,会中止响应其余操做
, 这是为了安全考虑。而 JavaScript 的垃圾回收在 100ms
甚至以上,对通常的应用还好,但对于 JavaScript 游戏和动画, 这种对连贯性要求比较高的应用,就麻烦了。这就是新引擎须要优化的点:避免垃圾回收形成的长时间中止响应
。
David 大叔主要介绍了2个优化方案,而这也是最主要的2个优化方案了:
这个和 Java 回收策略思想是一致的。目的是经过区分「临时」与「持久」对象
;多回收「临时对象区」(young generation), 少回收「持久对象区」(tenured generation),减小每次需遍历的对象,从而减小每次GC的耗时。Chrome 浏览器所使用的 V8 引擎就是采用的分代回收策略。如图:
这个方案的思想很简单,就是「每次处理一点,下次再处理一点,如此类推」。这种方案,虽然耗时短,但中断较多,带来了上下文切换频繁的问题。 Firefox 浏览器所使用的 JavaScript 引擎就是采用的增量回收策略。如图:
由于每种方案都其适用场景和缺点,所以在实际应用中,会根据实际状况选择方案。例如:若是大量对象都是长期「存活」,则分代处理优点也不大。
查看 Chrome 浏览器下的 CG 过程
使用快捷键 F12 或者 Ctrl+Shift+J 打开 Chrome 浏览器的「开发者工具」。
选择 Timeline 选项卡,在 Capture 选项中,只勾选 Memory。
设置完成后,点击最左边的 Record 按钮,而后就能够访问网页了。
复制代码
打开一个网站,例如:www.taobao.com,当网页加载完成后,点击 Stop,等待分析结果。
而后在 Chart View 上寻找内存急速降低的部分,查看对应的 Event Log,能够从中找到 GC 的日志。 具体过程以下图所示: