51、启动GNU加速
硬件加速的工作原理
浏览器接收到一个页面之后,将html解析成DOM树,浏览器解析渲染「html」的过程 按着一定的规则执行,DOM树和CSS树结合后构成浏览器形成页面的 渲染树 ; 渲染树中包含大量的渲染元素,每一个元素会被分配到一个图层中,每个图层又会被加载到GPU形成渲染纹理,而图层在GPU中 transform
是不会触发 repaint 的,这一点非常类似3D绘图功能,最终这些使用 transform
的图层都会由独立的合成器进程进行处理。也正因为这个原因,避免了频繁 repaint
。
使用 GPU 渲染元素
或许你会想,既然GPU加速这么好用,那我全用GPU好了,但实际上只有少数的几个可以触发GPU的硬件加速
- transform
- opacity
- filter
提高GPU的使用率,强制使用GPU渲染
因为2D transform
依旧会发生两次 Paint
操作,我们其实可以避免这种现象产生:
.transform1 {transform: translateZ(0);
}
.transform2 {transform: rotateZ(360deg);
}
强行设置 3D transform
,浏览器识别到这是一个3D动画,渲染前就创建了一个独立图层,图层中的动画则有GPU进行预处理并出发了硬件加速。
使用硬件加速的注意事项
使用硬件加速并不是十全十美的事情
- 过度使用引发的内存问题。
- 使用GPU渲染会影响字体的抗锯齿效果。这是因为GPU和CPU具有不同的渲染机制。即使最终硬件加速停止了,文本还是会在动画期间显示得很模糊。
52、事件冒泡与事件捕获
事件冒泡
和事件捕获
分别由微软
和网景
公司提出,这两个概念都是为了解决页面中事件流(事件发生顺序)的问题。
考虑下面这段代码,就不写html->head,body之类的代码了,自行脑补
<div id="outer"><p id="inner">Click me!</p>
</div>
事件冒泡
微软提出了名为事件冒泡(event bubbling)
的事件流。事件冒泡可以形象地比喻为把一颗石头投入水中,泡泡会一直从水底冒出水面。也就是说,事件会从最内层的元素开始发生,一直向上传播,直到document对象。
因此在事件冒泡的概念下在p元素上发生click事件的顺序应该是**p -> div -> body -> html -> document
**
事件捕获
网景提出另一种事件流名为事件捕获(event capturing)
。与事件冒泡相反,事件会从最外层开始发生,直到最具体的元素。
因此在事件捕获的概念下在p元素上发生click事件的顺序应该是**document -> html -> body -> div -> p
**
addEventListener的第三个参数
网景 和 微软 曾经的战争还是比较火热的,当时, 网景主张捕获方式,微软主张冒泡方式。后来 w3c 采用折中的方式,平息了战火,制定了统一的标准——先捕获再冒泡。
addEventListener的第三个参数就是为冒泡和捕获准备的.
addEventListener有三个参数:
element.addEventListener(event, function, useCapture)
第一个参数是需要绑定的事件
第二个参数是触发事件后要执行的函数
第三个参数默认值是false,表示在事件冒泡阶段调用事件处理函数;如果参数为true,则表示在事件捕获阶段调用处理函数。
事件捕获vs事件冒泡
当事件捕获和事件冒泡一起存在的情况,事件又是如何触发呢。
这里记被点击的DOM节点为target节点
- document 往 target节点,捕获前进,遇到注册的捕获事件立即触发执行
- 到达target节点,触发事件(对于target节点上,是先捕获还是先冒泡则捕获事件和冒泡事件的注册顺序,先注册先执行)
- target节点 往 document 方向,冒泡前进,遇到注册的冒泡事件立即触发
总结下就是:
- 对于非target节点则先执行捕获在执行冒泡
- 对于target节点则是先执行先注册的事件,无论冒泡还是捕获
<div id="s1">s1<div id="s2">s2</div>
</div>
<script>
s1.addEventListener("click",function(e){console.log("s1 冒泡事件");
},false);
s2.addEventListener("click",function(e){console.log("s2 冒泡事件");
},false);s1.addEventListener("click",function(e){console.log("s1 捕获事件");
},true);s2.addEventListener("click",function(e){console.log("s2 捕获事件");
},true);
</script>
当我们点击s2的时候,执行结果如下:
s1 捕获事件
my.html:19 s2 冒泡事件
my.html:27 s2 捕获事件
my.html:16 s1 冒泡事件
这里大体分析下执行结果
点击s2,click事件从document->html->body->s1->s2(捕获前进)
这里在s1上发现了捕获注册事件,则输出**“s1 捕获事件”**
到达s2,已经到达目的节点,
s2上注册了冒泡和捕获事件,先注册的冒泡后注册的捕获,则先执行冒泡,输出**“s2 冒泡事件”**
再在s2上执行后注册的事件,即捕获事件,输出**“s2 捕获事件”**
下面进入冒泡阶段,按照s2->s1->body->html->documen(冒泡前进)
在s1上发现了冒泡事件,则输出**“s1 冒泡事件”**
防止冒泡和捕获
w3c的方法是e.stopPropagation(),IE则是使用e.cancelBubble = true·
stopPropagation也是事件对象(Event)的一个方法,作用是阻止目标元素的冒泡事件,但是会不阻止默认行为。
取消默认事件
w3c的方法是e.preventDefault(),IE则是使用e.returnValue = false;·
preventDefault它是事件对象(Event)的一个方法,作用是取消一个目标元素的默认行为。既然是说默认行为,当然是元素必须有默认行为才能被取消,如果元素本身就没有默认行为,调用当然就无效了。
return false
javascript的return false只会阻止默认行为,而是用jQuery的话则既阻止默认行为又防止对象冒泡。
IE浏览器兼容
IE浏览器对addEventListener兼容性并不算太好,只有IE9以上可以使用。
要兼容旧版本的IE浏览器,可以使用IE的attachEvent函数
object.attachEvent(event, function)
两个参数与addEventListener相似,分别是事件和处理函数,默认是事件冒泡阶段调用处理函数,要注意的是,写事件名时候要加上"on"前缀(“onload”、"onclick"等)。
53、事件对象
什么是事件对象?
就是当你触发了一个事件以后,对该事件的一些描述信息
每一个事件都会有一个对应的对象来描述这些信息,我们就把这个对象叫做 事件对象
浏览器给了我们一个 黑盒子,叫做 window.event ,就是对事件信息的所有描述
box.onclick = function (e) {//兼容写法e = e || window.event
}
offsetX 和 offsetY
var box = document.querySelector('.box')box.onclick = function (e) {//兼容写法console.log(e.offsetX)}
clientX 和 clientY
是相对于浏览器窗口来计算的,从浏览器可视区域左上角开始,即是以浏览器滑动条此刻的滑动到的位置为参考点,随滑动条移动 而变化
var box = document.querySelector('.box')box.onclick = function (e) {//根据你浏览器的窗口来计算的,Y轴不包含导航地址栏和标签栏这些console.log(e.clientY)}
pageX 和 pageY
•是相对于整个页面的坐标点,不管有没有滚动,都是相对于页面拿到的坐标点 从页面左上角开始,即是以页面为参考点,不随滑动条移动而变化
var box = document.querySelector('.box')box.onclick = function (e) {//根据你浏览器的窗口来计算的,Y轴不包含导航地址栏和标签栏这些console.log(e.pageY)console.log(e.pageX)}
移动端ontouchstart等
ontouchstart、ontouchend、onclick这三个方法的执行顺序是ontouchstart > ontouchend > onclick
除了执行顺序不同以外,还有一个非常大的区别那就是onclick只在你快速点击并放开才会被执行,如果你点击一个区域,很迟才放开,那么onclick是不会执行的
事件绑定
事件绑定的两种方法
-
DOM0级事件绑定
curEle.onclick=function(){}
;
-
DOM2级事件绑定
-
标准浏览器:
curEle.addEventListener('click',function(){},false)
-
IE6-8:
curEle.attachEvent('onclick',function(){})
-
DOM0于DOM2事件绑定的区别
DOM0事件绑定的原理
给当前元素的某一私有属性(onXXX)赋值的过程
;(之前属性默认值是null,如果我们赋值了一个函数,就相当于绑定了一个方法)- 当我们赋值成功(赋值一个函数),此时浏览器会把DOM元素和赋值的的函数建立关联,以及建立DOM元素的行为监听,当某一行为被用户触发,浏览器会把赋值的函数执行;
DOM0事件绑定的特点
:
- 只有DOM元素天生拥有这个私有属性(onxxx事件私有属性),我们赋值的方法才叫事件绑定,否则属于设置自定义属性
- 移除事件绑定的时候,我们只需要赋值为null;
- 在DOM0事件绑定中,只能给当前元素的某一个事件行为绑定一个方法,绑定多个方法,最后一次的绑定的会替换前面绑定的
DOM2事件绑定的原理
-
DOM2事件绑定使用的
addEventListener/attachEvent方法都是在eventTarget这个内置类的原型上定义的
,我们调用的时候,首先要通过原型链找到这个方法,然后执行完成事件绑定的效果 -
浏览器会给当前元素的某个事件行为开辟一个事件池(事件队列)【浏览器有一个统一的事件池,每个元素绑定的行为都放在这里,通过相关标志区分】,当我们
通过 addEventListener/attachEvent进行事件绑定的时候,会把绑定的方法放在事件池中
; -
当元素的某一行为被触发,浏览器回到对应事件池中,把当前放在事件池的所有方法按序依次执行
特点
-
所有DOM0支持的行为,DOM2都可以用,DOM2还支持DOM0没有的事件行为(这样说比较笼统)
(核心)【浏览器会把一些常用事件挂载到元素对象的私有属性上,让我们可以实现DOM0事件绑定,DOM2:凡是浏览器给元素天生设置的事件在DOM2中都可以使用】
例如:onDOMContentLoaded
(所有的DOM0和IE6-8的DOM2都不支持)onDOMContentLoaded//当前浏览器中的DOM结构加载完成,就会触发这个事件
-
DOM2中可以给
当前元素的某一事件行为绑定多个不同方法
(因为绑定的所有方法都放在事件池中); -
事件的移除:
事件类型、绑定的方法、传播阶段三个完全一致,才可以完成移除
(因此在绑定方法时,尽量不要用匿名函数,否则不好移除)DOM0于DOM2事件绑定的区别DOM0事件绑定的原理
给当前元素的某一私有属性(onXXX)赋值的过程
;(之前属性默认值是null,如果我们赋值了一个函数,就相当于绑定了一个方法)- 当我们赋值成功(赋值一个函数),此时浏览器会把DOM元素和赋值的的函数建立关联,以及建立DOM元素的行为监听,当某一行为被用户触发,浏览器会把赋值的函数执行;
DOM0事件绑定的特点
:- 只有DOM元素天生拥有这个私有属性(onxxx事件私有属性),我们赋值的方法才叫事件绑定,否则属于设置自定义属性
- 移除事件绑定的时候,我们只需要赋值为null;
- 在DOM0事件绑定中,只能给当前元素的某一个事件行为绑定一个方法,绑定多个方法,最后一次的绑定的会替换前面绑定的
DOM2事件绑定的原理
-
DOM2事件绑定使用的
addEventListener/attachEvent方法都是在eventTarget这个内置类的原型上定义的
,我们调用的时候,首先要通过原型链找到这个方法,然后执行完成事件绑定的效果 -
浏览器会给当前元素的某个事件行为开辟一个事件池(事件队列)【浏览器有一个统一的事件池,每个元素绑定的行为都放在这里,通过相关标志区分】,当我们
通过 addEventListener/attachEvent进行事件绑定的时候,会把绑定的方法放在事件池中
; -
当元素的某一行为被触发,浏览器回到对应事件池中,把当前放在事件池的所有方法按序依次执行
特点
-
所有DOM0支持的行为,DOM2都可以用,DOM2还支持DOM0没有的事件行为(这样说比较笼统)
(核心)【浏览器会把一些常用事件挂载到元素对象的私有属性上,让我们可以实现DOM0事件绑定,DOM2:凡是浏览器给元素天生设置的事件在DOM2中都可以使用】
例如:onDOMContentLoaded
(所有的DOM0和IE6-8的DOM2都不支持)onDOMContentLoaded//当前浏览器中的DOM结构加载完成,就会触发这个事件
-
DOM2中可以给
当前元素的某一事件行为绑定多个不同方法
(因为绑定的所有方法都放在事件池中); -
事件的移除:
事件类型、绑定的方法、传播阶段三个完全一致,才可以完成移除
(因此在绑定方法时,尽量不要用匿名函数,否则不好移除)
DOMContentLoaded和load的区别
- DOMContentLoaded
当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完成加载。
- load
load 仅用于检测一个完全加载的页面,页面的html、css、js、图片等资源都已经加载完之后才会触发 load 事件。
54、函数防抖和节流
防抖(debounce)
所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
防抖函数分为非立即执行版和立即执行版。
非立即执行版:
function debounce(func,wait){let timeout=null;return function(){let context=this;let args=arguments;if(timeout) clearTimeout(timeout);timeout=setTimeout(()=>{func.apply(context,args);},wait);}
}
立即执行版:
function debounce(func,wait) {let timeout;return function () {let context = this;let args = arguments;if (timeout) clearTimeout(timeout);let callNow = !timeout;timeout = setTimeout(() => {timeout = null;}, wait)if (callNow) func.apply(context, args)}
}
节流(throttle)
**所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。**节流会稀释函数的执行频率。
对于节流,一般有两种方式可以实现,分别是时间戳版和定时器版。
时间戳版:
function throttle(func, wait) {let previous = 0;return function() {let now = Date.now();let context = this;let args = arguments;if (now - previous > wait) {func.apply(context, args);previous = now;}}
}
定时器版:
function throttle(func, wait) {let timeout;return function() {let context = this;let args = arguments;if (!timeout) {timeout = setTimeout(() => {timeout = null;func.apply(context, args)}, wait)}}
}
结合应用场景
防抖(debounce)
search搜索联想,用户在不断输入值时,用防抖来节约请求资源。
window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
节流(throttle)
鼠标不断点击触发,mousedown(单位时间内只触发一次)
监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
55、实现两端固定,中间自适应的布局
1).绝对定位法
绝对定位法原理是将左右两边使用absolute定位,因为绝对定位使其脱离文档流,后面的center会自然流动到他们上面,然后使用margin属性,留出左右元素的宽度,既可以使中间元素自适应屏幕宽度。
代码如下:
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>layout_box</title><link rel="stylesheet" type="text/css" href="../css/layout_box.css"></head><body><h3>实现三列宽度自适应布局</h3><div id = "left">我是左边</div><div id = "right">我是右边</div><div id = "center">我是中间</div></body>
</html>
css代码:
html,body{ margin: 0px;width: 100%; }
h3{height: 100px;margin:20px 0 0;}
#left,#right{width: 200px;height: 200px; background-color: #ffe6b8;position: absolute;top:120px;}
#left{left:0px;}
#right{right: 0px;}
#center{margin:2px 210px ;background-color: #eee;height: 200px; }
2).使用自身浮动法
<h3>使用自身浮动法定位</h3>
<div id = "left_self">我是左边</div>
<div id = "right_self">我是右边</div>
<div id = "center_self">我是中间</div>
#left_self,#right_self{ width: 200px;height: 200px; background-color: #ffe6b8 }
#left_self {float: left;}
#right_self{float: right;}
#center_self{margin: 0 210px;height: 200px; background-color: #a0b3d6}
css3新特性
在外围包裹一层div,设置为display:flex;中间设置flex:1;但是盒模型默认紧紧挨着,可以使用margin控制外边距。
代码:
<div id = "box"><div id = "left_box"></div><div id = "center_box"></div><div id = "right_box"></div></div>
#box{width:100%;display: flex; height: 100px;margin: 10px;}
#left_box,#right_box{width: 200px;height: 100px; margin: 10px; background-color: lightpink}
#center_box{ flex:1; height: 100px;margin: 10px; background-color: lightgreen}
grid
<div class="container"><div class="left"></div><div class="center"></div><div class="right"></div></div>
.container {display: grid;width: 100%;grid-template-rows: 100px;grid-template-columns: 300px auto 300px;}.left {background: red;}.center {background: yellow;}.right {background: blue;}
表格布局
<section class="layout table"><style>.layout.table .left-center-right {width: 100%;display: table;height: 100px;}.layout.table .left-center-right>div {display: table-cell;}.layout.table .left {width: 300px;background: red;}.layout.table .center {background: yellow;}.layout.table .right {width: 300px;background: blue;}</style><article class="left-center-right"><div class="left"></div><div class="center"><h1>表格布局的解决方案</h1><p>1.这是布局的中间部分</p><p>2.这是布局的中间部分</p></div><div class="right"></div></article>
</section>
圣杯布局
<style>body {min-width: 550px;}* {margin: 0;}#container {padding-left: 200px; padding-right: 150px;}#container .column {float: left;height: 300px;}#center {width: 100%;background: red;}#left {width: 200px;background: blue;margin-left: calc(-100% + -200px);}#right {width: 150px;background: pink;margin-right: -100%;}#footer {clear: both;}</style>
</head>
<body><div id="header"></div><div id="container"><div id="center" class="column"></div><div id="left" class="column"></div><div id="right" class="column"></div></div><div id="footer"></div>
</body>
其中有一点要注意的是margin、padding、left等设为百分比时是相对父元素的width
双飞翼布局
<style>body {min-width: 500px;}* {margin: 0;}#center {padding-left: 200px; padding-right: 150px;}.column {float: left;height: 300px;}#container {width: 100%;background: red;}#left {width: 200px;background: blue;margin-left: -100%;}#right {width: 150px;background: pink;margin-left: -150px;}#footer {clear: both;}</style>
</head>
<body><div id="header"></div><div id="container" class="column"><div id="center"></div></div><div id="left" class="column"></div><div id="right" class="column"></div><div id="footer"></div>
</body>
55、JS实现观察者模式
class Sub {constructor(){this.observe=[];}attach(val){this.observe.push(val);}getState(){return this.state;}setState(val){this.state=val;this.observe.forEach(watcher=>{watcher.update()})}
}
class Observe {constructor(sub){this.sub=sub;this.sub.attach(this);}update(){console.log('更新了'+this.sub.getState)}
}
56、手撕前端路由
// 这里用hash的路由方法实现
function Router() {this.routes = {}this.curUrl = ''this.init()
}Router.prototype.route = function (path, cb) {this.routes[path] = cb || function () {}
}Router.prototype.refresh = function () {this.curUrl = location.hash.slice(1) || '/'this.routes[this.curUrl] && this.routes[this.curUrl]()
}Router.prototype.init = function () {window.addEventListener('load', this.refresh.bind(this))window.addEventListener('hashchange', this.refresh.bind(this))
}// 用个例子试用一下
var router = new Router()
router.route('/', function () {var body = document.getElementById('page-type')body.innerHTML = '首页耶耶耶'
})
router.route('/news', function () {var body = document.getElementById('page-type')body.innerHTML = '新闻耶耶耶'
})
// 这里用history的路由方法实现
function Router() {this.routes = {}this.curUrl = ''this.init()
}Router.prototype.route = function (path, cb) {this.routes[path] = cb || function () {}
}Router.prototype.refresh = function () {this.curUrl = location.hash.slice(1) || '/'this.routes[this.curUrl] && this.routes[this.curUrl]()
}Router.prototype.init = function () {window.addEventListener('load', this.refresh.bind(this))window.addEventListener('popstate', this.refresh.bind(this))
}// 用个例子试用一下
var router = new Router()
router.route('/', function () {var body = document.getElementById('page-type')body.innerHTML = '首页耶耶耶'
})
router.route('/news', function () {var body = document.getElementById('page-type')body.innerHTML = '新闻耶耶耶'
})
57、Object.getPrototypeOf() 方法用于获取指定对象的原型对象(也就是__protp__的指向)
59、window中各类高度
网页可见区域高:document.body.clientHeight
网页正文全文高:document.body.scrollHeight
网页可见区域高(包括边线的高):document.body.offsetHeight
网页被卷去的高:document.body.scrollTop
屏幕分辨率高:window.screen.height
每个HTML元素都具有clientHeight offsetHeight scrollHeight offsetTop scrollTop 这5个和元素高度、滚动、位置相关的属性,单凭单词很难搞清楚分别代表什么意思之间有什么区别。通过阅读它们的文档总结出规律如下:
clientHeight和offsetHeight属性和元素的滚动、位置没有关系它代表元素的高度,其中:
**clientHeight:**包括padding但不包括border、水平滚动条、margin的元素的高度。对于inline的元素这个属性一直是0,单位px,只读元素。
**offsetHeight:**包括padding、border、水平滚动条,但不包括margin的元素的高度。对于inline的元素这个属性一直是0,单位px,只读元素。
接下来讨论出现有滚动条时的情况:
当本元素的子元素比本元素高且overflow=scroll时,本元素会scroll,这时:
scrollHeight: 因为子元素比父元素高,父元素不想被子元素撑的一样高就显示出了滚动条,在滚动的过程中本元素有部分被隐藏了,scrollHeight代表包括当前不可见部分的元素的高度。而可见部分的高度其实就是clientHeight,也就是scrollHeight>=clientHeight恒成立。在有滚动条时讨论scrollHeight才有意义,在没有滚动条时scrollHeight==clientHeight恒成立。单位px,只读元素。
scrollTop: 代表在有滚动条时,滚动条向下滚动的距离也就是元素顶部被遮住部分的高度。在没有滚动条时scrollTop==0恒成立。单位px,可读可设置。
offsetTop: 当前元素顶部距离最近父元素顶部的距离,和有没有滚动条没有关系。单位px,只读元素。
60、实现懒加载和预加载
<body><div class="imglist"><img src="./loading.gif" alt="" class="lazy" data-src="./1.jpg"><img src="./loading.gif" alt="" class="lazy" data-src="./2.jpg"><img src="./loading.gif" alt="" class="lazy" data-src="./3.jpg"><img src="./loading.gif" alt="" class="lazy" data-src="./4.jpg"><img src="./loading.gif" alt="" class="lazy" data-src="./5.jpg"><img src="./loading.gif" alt="" class="lazy" data-src="./6.jpg"><img src="./loading.gif" alt="" class="lazy" data-src="./7.jpg"><img src="./loading.gif" alt="" class="lazy" data-src="./8.jpg"></div>
</body>
<script>// onload是等所有的资源文件加载完毕以后再绑定事件
window.onload = function(){// 获取图片列表,即img标签列表var imgs = document.querySelectorAll('img');// 获取到浏览器顶部的距离function getTop(e){return e.offsetTop;}// 懒加载实现function lazyload(imgs){// 可视区域高度var h = window.innerHeight;//滚动区域高度var s = document.documentElement.scrollTop || document.body.scrollTop;for(var i=0;i<imgs.length;i++){//图片距离顶部的距离大于可视区域和滚动区域之和时懒加载if ((h+s)>getTop(imgs[i])) {// 真实情况是页面开始有2秒空白,所以使用setTimeout定时2s(function(i){setTimeout(function(){// 不加立即执行函数i会等于9// 隐形加载图片或其他资源,//创建一个临时图片,这个图片在内存中不会到页面上去。实现隐形加载var temp = new Image();temp.src = imgs[i].getAttribute('data-src');//只会请求一次// onload判断图片加载完毕,真是图片加载完毕,再赋值给dom节点temp.onload = function(){// 获取自定义属性data-src,用真图片替换假图片imgs[i].src = imgs[i].getAttribute('data-src')}},2000)})(i)}}}lazyload(imgs);// 滚屏函数window.onscroll =function(){lazyload(imgs);}
}
</script>
实现预加载的几种办法
- 使用HTML标签
<img src="http://pic26.nipic.com/20121213/6168183 0044449030002.jpg" style="display:none"/>
1
- 使用Image对象
<script src="./myPreload.js"></script>
1//myPreload.js文件var image= new Image()image.src="http://pic26.nipic.com/20121213/6168183 004444903000 2.jpg"
61、编写一个求和函数sum,使输入sum(2)(3)或输入sum(2,3),输出结果都为5
function sum(){var num = arguments[0];if(arguments.length == 1){return function(sec){return num+sec;}}else{for(var i = 1; i < arguments.length; i++){num += arguments[i];}return num;}
}
62、CORS
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。
它允许浏览器向跨源服务器,发出XMLHttpRequest
请求,从而克服了AJAX只能同源使用的限制。
CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
只要同时满足以下两大条件,就属于简单请求。
(1) 请求方法是以下三种方法之一:
- HEAD
- GET
- POST
(2)HTTP的头信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值
application/x-www-form-urlencoded
、multipart/form-data
、text/plain
简单请求
基本流程
对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin
字段。
下面是一个例子,浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin
字段。
GET /cors HTTP/1.1 Origin: http://api.bob.com Host: api.alice.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...
上面的头信息中,Origin
字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。
如果Origin
指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin
字段(详见下文),就知道出错了,从而抛出一个错误,被XMLHttpRequest
的onerror
回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。
如果Origin
指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。
Access-Control-Allow-Origin: http://api.bob.com Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: FooBar Content-Type: text/html; charset=utf-8
上面的头信息之中,有三个与CORS请求相关的字段,都以Access-Control-
开头。
(1)Access-Control-Allow-Origin
该字段是必须的。它的值要么是请求时Origin
字段的值,要么是一个*
,表示接受任意域名的请求。
(2)Access-Control-Allow-Credentials
该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true
,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true
,如果服务器不要浏览器发送Cookie,删除该字段即可。
(3)Access-Control-Expose-Headers
该字段可选。CORS请求时,XMLHttpRequest
对象的getResponseHeader()
方法只能拿到6个基本字段:Cache-Control
、Content-Language
、Content-Type
、Expires
、Last-Modified
、Pragma
。如果想拿到其他字段,就必须在Access-Control-Expose-Headers
里面指定。上面的例子指定,getResponseHeader('FooBar')
可以返回FooBar
字段的值。
withCredentials 属性
上面说到,CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials
字段。
Access-Control-Allow-Credentials: true
另一方面,开发者必须在AJAX请求中打开withCredentials
属性。
var xhr = new XMLHttpRequest(); xhr.withCredentials = true;
否则,即使服务器同意发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。
但是,如果省略withCredentials
设置,有的浏览器还是会一起发送Cookie。这时,可以显式关闭withCredentials
。
xhr.withCredentials = false;
需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin
就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie
也无法读取服务器域名下的Cookie。
非简单请求
预检请求
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT
或DELETE
,或者Content-Type
字段的类型是application/json
。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest
请求,否则就报错。
下面是一段浏览器的JavaScript脚本。
var url = 'http://api.alice.com/cors'; var xhr = new XMLHttpRequest(); xhr.open('PUT', url, true); xhr.setRequestHeader('X-Custom-Header', 'value'); xhr.send();
上面代码中,HTTP请求的方法是PUT
,并且发送一个自定义头信息X-Custom-Header
。
浏览器发现,这是一个非简单请求,就自动发出一个"预检"请求,要求服务器确认可以这样请求。下面是这个"预检"请求的HTTP头信息。
OPTIONS /cors HTTP/1.1 Origin: http://api.bob.com Access-Control-Request-Method: PUT Access-Control-Request-Headers: X-Custom-Header Host: api.alice.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...
"预检"请求用的请求方法是OPTIONS
,表示这个请求是用来询问的。头信息里面,关键字段是Origin
,表示请求来自哪个源。
除了Origin
字段,"预检"请求的头信息包括两个特殊字段。
(1)Access-Control-Request-Method
该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT
。
(2)Access-Control-Request-Headers
该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header
。
预检请求的回应
服务器收到"预检"请求以后,检查了Origin
、Access-Control-Request-Method
和Access-Control-Request-Headers
字段以后,确认允许跨源请求,就可以做出回应。
HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 01:15:39 GMT Server: Apache/2.0.61 (Unix) Access-Control-Allow-Origin: http://api.bob.com Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: X-Custom-Header Content-Type: text/html; charset=utf-8 Content-Encoding: gzip Content-Length: 0 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Content-Type: text/plain
上面的HTTP回应中,关键的是Access-Control-Allow-Origin
字段,表示http://api.bob.com
可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。
Access-Control-Allow-Origin: *
如果服务器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest
对象的onerror
回调函数捕获。控制台会打印出如下的报错信息。
XMLHttpRequest cannot load http://api.alice.com. Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.
服务器回应的其他CORS相关字段如下。
Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: X-Custom-Header Access-Control-Allow-Credentials: true Access-Control-Max-Age: 1728000
(1)Access-Control-Allow-Methods
该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。
(2)Access-Control-Allow-Headers
如果浏览器请求包括Access-Control-Request-Headers
字段,则Access-Control-Allow-Headers
字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。
(3)Access-Control-Allow-Credentials
该字段与简单请求时的含义相同。
(4)Access-Control-Max-Age
该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。
4.3 浏览器的正常请求和回应
一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin
头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin
头信息字段。
下面是"预检"请求之后,浏览器的正常CORS请求。
PUT /cors HTTP/1.1 Origin: http://api.bob.com Host: api.alice.com X-Custom-Header: value Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...
上面头信息的Origin
字段是浏览器自动添加的。
下面是服务器正常的回应。
Access-Control-Allow-Origin: http://api.bob.com Content-Type: text/html; charset=utf-8
上面头信息中,Access-Control-Allow-Origin
字段是每次回应都必定包含的。
63、为什么在项目中data需要使用return返回数据呢?
组件是一个可复用的实例,当你引用一个组件的时候,组件里的data是一个普通的对象,所有用到这个组件的都引用的同一个data,就会造成数据污染。
不使用return包裹的数据会在项目的全局可见,会造成变量污染;使用return包裹后数据中变量只在当前组件中生效,不会影响其他组件。
data必须是函数的原因
当一个组件被定义, data 必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。如果 data 仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!通过提供 data 函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象。
因为在JS 中只有函数才存在作用域,data是一个函数时,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响!
64、微信小程序的更新机制 && 如何让微信用户更新小程序
启动
小程序启动会有两种情况,一种是「冷启动」,一种是「热启动」。 假如用户已经打开过某小程序,然后在一定时间内再次打开该小程序,此时无需重新启动,只需将后台态的小程序切换到前台,这个过程就是热启动;冷启动指的是用户首次打开或小程序被微信主动销毁后再次打开的情况,此时小程序需要重新加载启动。
更新机制
小程序冷启动时如果发现有新版本,将会异步下载新版本的代码包,并同时用客户端本地的包进行启动,即新版本的小程序需要等下一次冷启动才会应用上。 如果需要马上应用最新版本,可以使用 wx.getUpdateManager API 进行处理。
运行机制
- 小程序没有重启的概念
- 当小程序进入后台,客户端会维持一段时间的运行状态,超过一定时间后(目前是5分钟)会被微信主动销毁
- 当短时间内(5s)连续收到两次以上收到系统内存告警,会进行小程序的销毁
小程序重新初始化时会触发onLaunch事件. onLaunch事件会触发在页面onShow事件之前.获取小程序更新版本可以写在onLaunch里.
// 在app.js里写下以下代码onLaunch () {if (wx.canIUse('getUpdateManager')) {const updateManager = wx.getUpdateManager()updateManager.onCheckForUpdate(function (res) {console.log('onCheckForUpdate====', res)// 请求完新版本信息的回调if (res.hasUpdate) {console.log('res.hasUpdate====')updateManager.onUpdateReady(function () {wx.showModal({title: '更新提示',content: '新版本已经准备好,是否重启应用?',success: function (res) {console.log('success====', res)// res: {errMsg: "showModal: ok", cancel: false, confirm: true}if (res.confirm) {// 新的版本已经下载好,调用 applyUpdate 应用新版本并重启updateManager.applyUpdate()}}})})updateManager.onUpdateFailed(function () {// 新的版本下载失败wx.showModal({title: '已经有新版本了哟~',content: '新版本已经上线啦~,请您删除当前小程序,重新搜索打开哟~'})})}})}}
65、构造函数&&原型
构造函数
创建一个构造函数,专门用来创建Person对象的
构造函数就是一个普通的函数,创建方式和普通函数没有区别,
不同的是构造函数习惯上首字母大写
构造函数和普通函数的区别就是调用方式的不同
普通函数是直接调用,而构造函数需要使用new关键字来调用
构造函数的执行流程:
1.立刻创建一个新的对象
2.将新建的对象设置为函数中this,在构造函数中可以使用this来引用新建的对象
3.逐行执行函数中的代码
4.将新建的对象作为返回值返回
使用同一个构造函数创建的对象,我们称为一类对象,也将一个构造函数称为一个类。
我们将通过一个构造函数创建的对象,称为是该类的实例
this的情况:
1.当以函数的形式调用时,this是window
2.当以方法的形式调用时,谁调用方法this就是谁
3.当以构造函数的形式调用时,this就是新创建的那个对象
如果方法是在构造函数内部创建的, 也就是构造函数每执行一次就会创建一个新的方法,也就是所有实例的方法都是唯一的。这样就导致了构造函数执行一次就会创建一个新的方法,执行10000次就会创建10000个新的方法,而10000个方法都是一摸一样的。这是完全没有必要,完全可以使所有的对象共享同一个方法,这就利用了prototype
prototype和_proto_
Javascript中所有的对象都是Object的实例,并继承Object.prototype的属性和方法,也就是说,Object.prototype是所有对象的爸爸。
在对象创建时,就会有一些预定义的属性,其中定义函数的时候,这个预定义属性就是prototype,这个prototype是一个普通的对象。
而定义普通的对象的时候,就会生成一个__proto__,这个__proto__指向的是这个对象的构造函数的prototype.
我们所创建的每一个函数,解析器都会向函数中添加一个属性prototype
这个属性对应着一个对象,这个对象就是我们所谓的原型对象
如果函数作为普通函数调用prototype没有任何作用
当函数以构造函数的形式调用时,它所创建的对象中都会有一个隐含的属性,
指向该构造函数的原型对象,我们可以通过__proto__来访问该属性
原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象,
我们可以将对象中共有的内容,统一设置到原型对象中。
当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,
如果没有则会去原型对象中寻找,如果找到则直接使用
以后我们创建构造函数时,可以将这些对象共有的属性和方法,统一添加到构造函数的原型对象中,
这样不用分别为每一个对象添加,也不会影响到全局作用域,就可以使每个对象都具有这些属性和方法了
66、js中实例方法、静态方法和原型方法
实例方法
构造函数中this上添加的成员 ,在Cat构造方法里面,定义在this中的变量和方法,只有实例才能访问到:如this.name,this.move,this.eat这些都是实例拥有,无法通过Cat直接调用。
function Cat(name){this.name = namethis.move = function() {console.log('移动')}this.eat = function() {console.log(`${this.name}爱吃鱼`)}
}
Cat.eat()
let cat=new Cat();
cat.eat() //tom爱吃鱼 //这是实例方法
静态方法
构造函数本身上添加的成员
下面的Cat.eat就是构造函数的静态方法,不能通过实例调用
function Cat(name){this.move = function() {console.log(1)}
}
//直接定义在Cat构造函数中,实例不能调用
Cat.eat = function() {console.log(`${this.name}爱吃鱼`)}
构造函数调用
Cat.eat() //Cat爱吃鱼
Cat.move() //Cat.move is not a function
let cat = new Cat()
cat.eat() //cat.eat is not a function
原型方法
原型中的方法实例和构造函数都可以访问到
function Cat() {
}
Cat.eat = function() {console.log('静态方法')
}
Cat.prototype.eat = function() {console.log('原型方法')
}
let cat = new Cat()
Cat.eat() //静态方法
Cat.prototype.eat() //原型方法,不用prototype就是打印静态方法cat.eat() //原型方法
简而言之,实例方法就是只有实例可以调用,静态方法只有构造函数可以调用,原型方法是实例和构造函数都可以调用,是共享的方法。
像Promise.all和Promise.race这些就是静态方法,Promise.prototype.then这些就是原型方法,new 出来的实例可以调用
67、JS中继承的几种方式
1.原型链继承
function Person (name, age) {this.name = name;this.age = age;
}
Person.prototype.say = function(){console.log('hello, my name is ' + this.name);
};
function Man() {
}
Man.prototype = new Person('pursue');
var man1 = new Man();
man1.say(); //hello, my name is pursue
var man2 = new Man();
console.log(man1.say === man2.say);//true
console.log(man1.name === man2.name);//true
此时person的name和age在man的prototype,但原型方法仍然在person的prototype,因为name和age是实例属性,而say是实例方法
2、利用构造函数继承
function Person (name, age) {this.name = name;this.age = age;
}
Person.prototype.say = function(){console.log('hello, my name is ' + this.name);
};
function Man(name, age) {Person.apply(this, arguments);
}
//Man.prototype = new Person('pursue');
var man1 = new Man('joe');
var man2 = new Man('david');
console.log(man1.name === man2.name);//false
man1.say(); //say is not a function
优点:
- 解决了原型链继承中的子类共享父类属性的问题
- 创建的子类实例可以向父类传递参数
- 可以实现多继承,call改变父类的this
缺点:
- 实例是子类的实例,不是父类的
- 只能继承父类的实例属性和方法,不能继承父类原型上的方法
- 无法实现函数复用,每个子类都有父类函数的属性和方法的副本,当child调用Parent上的方法时,Parent内部的this指向的是child,Parent内部的this上的属性和方法都被复制到了child上面,如果每个子类的实例都复制一遍父类的属性和方法,就会占用很大的内存,而且当父类的方法发生改变了时,已经创建好的子类实例并不能更新方法,因为已经复制了原来的父类方法当成自己的方法了。
3、组合继承(原型链继承与构造继承)
function Child(name) {Parent.call(this,name) //构造继承 ,第二次调用父类
}
//原型链继承
Child.prototype=new Parent()
Child.prototype.constructor=Child//因重写原型而失去constructor属性,所以要对constrcutor重新赋值var child=new Child("yzh") //子类的实例向父类传递参数,第一次调用父类
console.log(child.name)
child.introduce()
child.hobby("sing")
console.log(child instanceof Parent) //true
console.log(child instanceof Child) //true
优点:结合了原型链继承和构造继承的优点
- 子类可向父类传参
- 实例既是子类的实例,也是父类的实例
- 多个实例之间不存在公用父类的引用属性的问题
- 实例可以继承父类实例的属性和方法,也可以继承原型上的属性和方法
缺点:这种方式调用了两次父类的构造函数,生成了两份实例,相同的属性既存在于实例中也存在于原型中
4、寄生组合继承
function Person (name, age) {this.name = name;this.age = age;}
Person.prototype.say = function(){console.log('hello, my name is ' + this.name);
};
function Man(name, age) {Person.apply(this, arguments);
}
Man.prototype = Object.create(Person.prototype);//a.
Man.prototype.constructor = Man;//b.
var man1 = new Man('pursue');
var man2 = new Man('joe');
console.log(man1.say == man2.say);
console.log(man1.name == man2.name);
其实寄生组合继承和上面的组合继承区别仅在于构造子类原型对象的方式上(a.和b.
),这里用到了Object.creat(obj)
方法,该方法会对传入的obj对象进行浅拷贝,类似于:
function create(obj){function T(){};T.prototype = obj;return new T();
}
组合继承(构造函数和原型的组合)会调用两次父类构造函数的代码,因此引入寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的方式来继承方法,而不需要为子类指定原型而调用父类的构造函数,我们需要拿到的仅仅是父类原型的一个副本。因此可以通过传入子类和父类的构造函数作为参数,首先创建父类原型的一个复本,并为其添加constrcutor,最后赋给子类的原型。这样避免了调用两次父类的构造函数,为其创建多余的属性。
68、for in 和for of的区别
for in通常用来遍历对象
for in 可以遍历到该对象的原型方法method,如果不想遍历原型方法和属性的话,可以在循环内部判断一下,hasOwnPropery方法可以判断某属性是否是该对象的实例属性。
同样可以通过ES5的Object.keys(myObject)获取对象的实例属性组成的数组,不包括原型方法和属性。
for of通常用来遍历数组
for in遍历的是数组的索引(即键名),而for of遍历的是数组元素值。
for of遍历的只是数组内的元素,而不包括数组的原型属性method和索引name
for…of适用遍历数/数组对象/字符串/map/set等拥有迭代器对象的集合.但是不能遍历对象,因为没有迭代器对象.与forEach()不同的是,它可以正确响应break、continue和return语句.使用foreach遍历数组的话,使用break不能中断循环,使用return也不能返回到外层函数。
69、内置对象/宿主对象/自定义对象的区别?
1、内置对象:系统所提供的对象;如Object、Array、Math、Date等等。
2、宿主对象:JS所运行的环境提供的对象比如:BOM中的Window、DOM中的document。
3、自定义对象:自定义构造函数所创建的对象。
71、判断一个对象是否存在
现在,我们要判断一个全局对象myObj是否存在,如果不存在,就对它进行声明。
if (!myObj) {var myObj = { };}
if (!window.myObj) {var myObj = { };}
上面这种写法的缺点在于,在某些运行环境中(比如V8、Rhino),window未必是顶层对象。所以,考虑改写成:
if (!this.myObj) {this.myObj = { };}
还可以使用typeof运算符,判断myObj是否有定义。
if (typeof myObj == "undefined") {var myObj = { };}
这是目前使用最广泛的判断javascript对象是否存在的方法。
由于在已定义、但未赋值的情况下,myObj的值直接等于undefined,所以上面的写法可以简化:
if (myObj == undefined) {var myObj = { };}
上面的写法在"精确比较"(===)的情况下,依然成立:
根据javascript的语言设计,undefined == null,所以比较myObj是否等于null,也能得到正确结果:
if (myObj == null) {var myObj = { };}
还可以使用in运算符,判断myObj是否为顶层对象的一个属性:
if (!('myObj' in window)) {window.myObj = { };}
最后,使用hasOwnProperty方法,判断myObj是否为顶层对象的一个属性:
if (!this.hasOwnProperty('myObj')) {this.myObj = { };}
72、Object.create(null) 和 {} 区别
Object.create(null)
没有继承任何原型方法,也就是说它的原型链没有上一层。即没有_proto_