1. 跨标签页通讯
不同标签页间的通讯,本质原理就是去运用一些可以 共享的中间介质,因此比较常用的有以下方法:
1:设置同域下共享的localStorage
与监听window.onstorage
会受到浏览器隐身模式等的限制
一、localStorage (1)localStorage是什么? localStorage对象在修订过的HTML5规范中作为持久保存在客户端数据的方案取代了globalStorage,是Storage的实例。 注意:要访问一个localStorage对象,页面必须来自同一个域名(子域名无效),使用同一种协议,在同一个端口上。相当于globalStorage[localhost.host]。 (2)localStorage怎么用? localStorage作为Storage的实例,可以使用Storage实例的方法。
localStorage.setItem('name','lwf');
localStorage.age=21;//注意后填加的放在localStorage的前面
console.log(localStorage.getItem('name'));//lwf
console.log(localStorage.age);//21
console.log(localStorage);//Storage {age: "21", name: "lwf", length: 2}
console.log(localStorage.key(0));//age,注意index从0开始
console.log(localStorage.length);//2
localStorage.removeItem('name');
console.log(localStorage.getItem('name'));//null
localStorage.clear();
console.log(localStorage);//Storage {length: 0}
复制代码
(3)怎么使用localStorage实现多标签之间的通信?
index.html:
<input type="text">
<button id="btn">Click</button>
<script>
window.onload=function(){
var oBtn=document.getElementById("btn");
var oInput=document.getElementsByTagName("input")[0];
oBtn.onclick=function(){
var val=oInput.value;
localStorage.setItem("value",val);
}
}
</script>
复制代码
test.html:
<script>
window.addEventListener("storage",function(event){
console.log("value is"+localStorage.getItem("value"));
console.log("key is"+event.newValue);
},false);
</script>
复制代码
注意:只能实现同一浏览器相同域名、相同协议、相同端口下的多个标签页之间的通信。不同浏览器没有该效果。
(4)localStorage可以实现同一浏览器多个标签页之间通信的原理 localStorage是Storage对象的实例。对Storage对象进行任何修改,都会在文档上触发storage事件。当通过属性或者setItem()方法保存数据,使用delete操作符或removeItem()删除数据,或者调用clear()方法时,都会发生该事件。这个事件的event对象有以下属性:
domin
:发生变化的存储空间的域名;key
:设置或者删除的键名;newValue
:如果是设置值,则为新值;如果是删除值,则是null;oldValue
:键被更改之前的值;
2:设置共享cookie
与不断轮询脏检查(setInterval
)
(1)cookie是什么? HTTP Cookie,通常直接叫做cookie,最初是在客户端用于存储回话信息的。该标准要求服务器对任意HTTP请求发送Set-Cookie HTTP头作为响应的一部分,其中包含回话信息。浏览器会存储这样的回话信息,并在这之后,通过每个请求添加Cookie HTTP头将信息发回服务器。
(2)cookie怎么用?
在JavaScript中,cookie的接口即document.cookie
不太友好,可以自己封装相应的接口。
基本cookie操作:读取、写入、删除
//代码来源于JavaScript高级程序设计
var CookieUtil={
get:function(name){
var cookieName=encodeURIComponent(name)+"=",
cookieStart=document.cookie.indexOf(cookieName),
cookieValue=null;
if(cookieStart>-1){
var cookieEnd=document.cookie.indexOf(";",cookieStart);
if(cookieEnd==-1){
cookieEnd=document.cookie.length;
}
cookieValue=decodeURIComponent(document.cookie.substring(cookieStart+cookieName.length,cookieEnd));
}
return cookieValue;
},
set:function(name,value,expires,path,domain,secure){//cookie的构成:名称、值、失效时间(何时应停止向浏览器发送cookie)、路径(向服务器发送cookie的特定域的路径)、域(cookie对于哪个域是有效的)、安全标志(指定后只能在使用SSL连接时才发送到服务器)
var cookieText=encodeURIComponent(name) + "=" +encodeURIComponent(value);
if(expires instanceof Date){
cookieText += "; expires="+expires.toGMTString();//时间为GMT格式,注意信息之间用“; ”分割
}
if(path){
cookieText += "; path="+path;
}
if(domain){
cookieText += "; domain="+domain;
}
if(secure){
cookieText += "; secure";
}
document.cookie = cookieText;
},
unset:function(name,path,domain,secure){//没有直接删除cookie的方法
this.set(name,"",new Date(0),path,domain,secure);//使用相同路径、域、安全选项再次设置cookie,并将失效时间设置为过去的时间
}
};
复制代码
使用:
//设置cookie
CookieUtil.set("name",'lwf');
CookieUtil.set("age",21);
// 读取cookie
console.log(CookieUtil.get("name"));
console.log(CookieUtil.get("age"));
// 删除cookie
CookieUtil.unset("name");
CookieUtil.unset("age");
复制代码
(3)怎么使用cookie实现多标签之间的通信?
index.html
<input type="text">
<button id="btn">Click</button>
<script>
window.onload=function(){
var oBtn=document.getElementById("btn");
var oInput=document.getElementsByTagName("input")[0];
oBtn.onclick=function(){
var val=oInput.value;
CookieUtil.set("name",val);
console.log(CookieUtil.get("name"));
}
var cookieUtil={//...}
}
<script>
复制代码
test.html
window.onload=function(){
var getCookie = function( keyName){
var items = [] , json = {};
var cookie = document.cookie;
if( cookie.length > 0 ){
items = cookie.split(';');
for(var i = 0;i < items.length;i++){
json[items[i].split('=')[0]] = items[i].split('=')[1] ;
}
return unescape(json[keyName]);
}else{
return '';
}
}
setInterval(function(){
console.log("name=" + getCookie("name"));
}, 10000);
};
复制代码
4、cookie能实现同一浏览器多个标签页之间通信的原理 cookie的path:一个页面产生的cookie只能被与这个页面的同一目录或者其他子目录下的页面访问。因此,通常把cookie的path设置为一个更高级别的目录,从而使更多的页面共享cookie,实现多页面之间相互通信。
补充: path
:cookie所在的目录,默认为/
,即根目录, 通常用来解决同域下cookie的访问问题 domain
:cookie所在的域,默认为请求的地址,通过设置document.domain
可以实现跨域访问
3:通过父页面window.open()
和子页面postMessage
window.open('about: blank')
和
tab.location.href = '*'
postMessage
是html5引入的API可以更方便、有效、安全的解决这些问题。postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。
postMessage(data,origin)方法接受两个参数:
-
data:要传递的数据,
html5规范中提到该参数可以是JavaScript的任意基本类型或可复制的对象,然而并不是所有浏览器都做到了这点儿,部分浏览器只能处理字符串参数,所以我们在传递参数的时候需要使用JSON.stringify()方法对对象参数序列化,在低版本IE中引用json2.js可以实现类似效果。 -
origin:字符串参数,指明目标窗口的源,
协议+主机+端口号[+URL]
,URL会被忽略,所以可以不写,这个参数是为了安全考虑,someWindow.postMessage()
方法只会在someWindow所在的源(url的protocol, host, port)和指定源一致时才会成功触发message event,当然如果愿意也可以将参数设置为"*
",someWindow可以在任意源,如果要指定和当前窗口同源的话设置为"/
"。
MessageEvent的属性:
data:顾名思义,是传递来的messagesource:发送消息的窗口对象
origin:发送消息窗口的源(协议+主机+端口号)
同域父子页面间通讯:
父页面a.html:
//> localhost:9011/a.html
<h1 class="header">page A</h1>
<div class="mb20">
<textarea name="ta" id="data" cols="30" rows="5">hello world</textarea>
<button style="font-size:20px;" onclick="send()">post message</button>
</div>
<!-- 不跨域的情况 -->
<iframe src="b.html" id="child" style="display: block; border: 1px dashed #ccc; height: 300px;"></iframe>
<script>
function send() {
var data = document.querySelector('#data').value;
// 注意: 只会触发当前window对象的message事件
// 也可以访问子页面的window对象,触发子页面的message事件 (window.frames[0].postMessage(...))
// window.postMessage(data, '/');
// data = {name: 'sandy', age: 20, fav: {sing: true, shop: false}}; // 也可以传普通对象
window.frames[0].postMessage(data, '/'); // 触发同域子页面的message事件
//window.frames[0].postMessage(data, 'http://localhost:9022/'); // 触发跨域子页面的messag事件
}
// 当前页面执行 window.postMessage(..)
// 或
// 子页面执行 parent.postMessage(...) 都会触发下面的回调, messageEvent.source不同而已
window.addEventListener('message', function(messageEvent) {
var data = messageEvent.data;// messageEvent: {source, currentTarget, data}
console.info('message from child:', data);
}, false);
</script>
复制代码
子页面b.html
//> localhost:9011/b.html
<h1 class="header">page B</h1>
<input type="text" id="inp" value="some contents..">
<button onclick="send()">send</button>
<script>
window.addEventListener('message', function(ev) {
// if (ev.source !== window.parent) {return;}
var data = ev.data;
console.info('message from parent:', data);
}, false);
function send() {
var data = document.querySelector('#inp').value;
// window.postMessage(data, '*'); // 触发当前页面的message事件
parent.postMessage(data, '*'); // 触发父页面的message事件
// parent.postMessage(data, 'http://localhost:9011/'); // 若父页面的域名和指定的不一致,则postMessage失败
}
</script>
复制代码
跨域父子页面间通讯:
父页面a.html:
//> localhost:9011/a.html
<h1 class="header">page A</h1>
<div class="mb20">
<textarea name="ta" id="data" cols="30" rows="5">hello world</textarea>
<button style="font-size:20px;" onclick="send()">post message</button>
</div>
<!-- 跨域的情况 -->
<iframe src="http://localhost:9022/b.html" id="child" style="display: block; border: 1px dashed #ccc; height: 300px;"></iframe>
<script>
function send() {
var data = document.querySelector('#data').value;
window.frames[0].postMessage(data, 'http://localhost:9022/'); // 触发跨域子页面的messag事件
}
window.addEventListener('message', function(messageEvent) {
var data = messageEvent.data;
console.info('message from child:', data);
}, false);
</script>
复制代码
子页面b.html
//> localhost:9022/b.html
<h1 class="header">page B</h1>
<input type="text" id="inp" value="some contents..">
<button onclick="send()">send</button>
<script>
window.addEventListener('message', function(ev) {
// if (ev.source !== window.parent) {return;}
var data = ev.data;
console.info('message from parent:', data);
}, false);
function send() {
var data = document.querySelector('#inp').value;
parent.postMessage(data, 'http://localhost:9011/'); // 若父页面的域名和指定的不一致,则postMessage失败
// parent.postMessage(data, '*'); // 触发父页面的message事件
}
</script>
复制代码
4:借助服务端或者中间层实现
2. 浏览器架构
- 用户界面
- 主进程
- 内核
渲染引擎
js 引擎
执行栈
事件触发线程
消息队列
微任务
宏任务
网络异步线程
定时器线程
3. 浏览器下事件循环(Event Loop)
事件循环是指: 执行一个宏任务,然后执行清空微任务列表,循环再执行宏任务,再清微任务列表
- 微任务
microtask(jobs)
:promise / ajax / Object.observe(该方法已废弃)
- 宏任务
macrotask(task)
:setTimout / script / IO / UI Rendering
4. 从输入 url 到展示的过程
- DNS 解析
- TCP 三次握手
- 发送请求,分析 url,设置请求报文(头,主体)
- 服务器返回请求的文件 (html)
- 浏览器渲染
1.HTML parser --> DOM Tree
标记化算法,进行元素状态的标记
dom 树构建
2.CSS parser --> Style Tree
3.attachment --> Render Tree
结合 dom树 与 style树,生成渲染树
4.layout: 布局
5.GPU painting: 像素绘制页面5. 重绘与回流
当元素的样式发生变化时,浏览器需要触发更新,重新绘制元素。这个过程中,有两种类型的操作,即重绘与回流。
- 重绘(repaint): 当元素样式的改变不影响布局时,浏览器将使用重绘对元素进行更新,此时由于只需要UI层面的重新像素绘制,因此 损耗较少
- 回流(reflow): 当元素的尺寸、结构或触发某些属性时,浏览器会重新渲染页面,称为回流。此时,浏览器需要重新经过计算,计算后还需要重新页面布局,因此是较重的操作。会触发回流的操作:
- clientWidth、clientHeight、clientTop、clientLeft
- offsetWidth、offsetHeight、offsetTop、offsetLeft
- scrollWidth、scrollHeight、scrollTop、scrollLeft
- getComputedStyle()
- getBoundingClientRect()
- scrollTo()
回流必定触发重绘,重绘不一定触发回流。重绘的开销较小,回流的代价较高。
最佳实践:
css
- 避免使用
table
布局 - 将动画效果应用到
position
属性为absolute
或fixed
的元素上
javascript
- 避免频繁操作样式,可汇总后统一 一次修改
- 尽量使用
class
进行样式修改 - 减少
dom
的增删次数,可使用 字符串 或者documentFragment
一次性插入 - 极限优化时,修改样式可将其
display: none
后修改 - 避免多次触发上面提到的那些会触发回流的方法,可以的话尽量用 变量存住
6. 存储
我们经常需要对业务中的一些数据进行存储,通常可以分为 短暂性存储 和 持久性储存。
短暂性的时候,我们只需要将数据存在内存中,只在运行时可用
持久性存储,可以分为 浏览器端 与 服务器端
cookie
: 通常用于存储用户身份,登录状态等- http 中自动携带, 体积上限为 4K, 可自行设置过期时间
localStorage / sessionStorage
: 长久储存/窗口关闭删除, 体积限制为 4~5MindexDB
- 分布式缓存 redis
- 数据库