如何依据设计稿进行挪动端适配?
挪动端适配次要有两个维度:
- 适配不同像素密度, 针对不同的像素密度,应用 CSS 媒体查问,抉择不同精度的图片,以保障图片不会失真;
- 适配不同屏幕大小, 因为不同的屏幕有着不同的逻辑像素大小,所以如果间接应用 px 作为开发单位,会使得开发的页面在某一款手机上能够精确显示,然而在另一款手机上就会失真。为了适配不同屏幕的大小,应依照比例来还原设计稿的内容。
为了能让页面的尺寸自适应,能够应用 rem,em,vw,vh 等绝对单位。
localStorage sessionStorage cookies 有什么区别?
localStorage:以键值对的形式存储 贮存工夫没有限度 永恒失效 除非本人删除记录sessionStorage:当页面敞开后被清理与其余相比不能同源窗口共享 是会话级别的存储形式cookies 数据不能超过4k 同时因为每次http申请都会携带cookie 所有cookie只适宜保留很小的数据 如会话标识
代码输入问题
function A(){}function B(a){ this.a = a;}function C(a){ if(a){this.a = a; }}A.prototype.a = 1;B.prototype.a = 1;C.prototype.a = 1;console.log(new A().a);console.log(new B().a);console.log(new C(2).a);
输入后果:1 undefined 2
解析:
- console.log(new A().a),new A()为构造函数创立的对象,自身没有a属性,所以向它的原型去找,发现原型的a属性的属性值为1,故该输入值为1;
- console.log(new B().a),ew B()为构造函数创立的对象,该构造函数有参数a,但该对象没有传参,故该输入值为undefined;
- console.log(new C(2).a),new C()为构造函数创立的对象,该构造函数有参数a,且传的实参为2,执行函数外部,发现if为真,执行this.a = 2,故属性a的值为2。
说下对 JS 的理解吧
是基于原型的动静语言,次要独特个性有 this、原型和原型链。
JS 严格意义上来说分为:语言规范局部(ECMAScript)+ 宿主环境局部
语言规范局部
2015 年公布 ES6,引入诸多新个性使得可能编写大型项目变成可能,规范自 2015 之后以年号代号,每年一更
宿主环境局部
- 在浏览器宿主环境包含 DOM + BOM 等
- 在 Node,宿主环境包含一些文件、数据库、网络、与操作系统的交互等
浅拷贝
// 这里只思考对象类型function shallowClone(obj) { if(!isObject(obj)) return obj; let newObj = Array.isArray(obj) ? [] : {}; // for...in 只会遍历对象本身的和继承的可枚举的属性(不含 Symbol 属性) for(let key in obj) { // obj.hasOwnProperty() 办法只思考对象本身的属性 if(obj.hasOwnProperty(key)) { newObj[key] = obj[key]; } } return newObj;}
事件流传机制(事件流)
冒泡和捕捉
参考 前端进阶面试题具体解答
HTML5的离线贮存怎么应用,它的工作原理是什么
离线存储指的是:在用户没有与因特网连贯时,能够失常拜访站点或利用,在用户与因特网连贯时,更新用户机器上的缓存文件。
原理:HTML5的离线存储是基于一个新建的 .appcache
文件的缓存机制(不是存储技术),通过这个文件上的解析清单离线存储资源,这些资源就会像cookie一样被存储了下来。之后当网络在处于离线状态下时,浏览器会通过被离线存储的数据进行页面展现
应用办法: (1)创立一个和 html 同名的 manifest 文件,而后在页面头部退出 manifest 属性:
<html lang="en" manifest="index.manifest">
(2)在 cache.manifest
文件中编写须要离线存储的资源:
CACHE MANIFEST #v0.11 CACHE: js/app.js css/style.css NETWORK: resourse/logo.png FALLBACK: / /offline.html
- CACHE: 示意须要离线存储的资源列表,因为蕴含 manifest 文件的页面将被主动离线存储,所以不须要把页面本身也列出来。
- NETWORK: 示意在它上面列出来的资源只有在在线的状况下能力拜访,他们不会被离线存储,所以在离线状况下无奈应用这些资源。不过,如果在 CACHE 和 NETWORK 中有一个雷同的资源,那么这个资源还是会被离线存储,也就是说 CACHE 的优先级更高。
- FALLBACK: 示意如果拜访第一个资源失败,那么就应用第二个资源来替换他,比方下面这个文件示意的就是如果拜访根目录下任何一个资源失败了,那么就去拜访 offline.html 。
(3)在离线状态时,操作 window.applicationCache
进行离线缓存的操作。
如何更新缓存:
(1)更新 manifest 文件
(2)通过 javascript 操作
(3)革除浏览器缓存
注意事项:
(1)浏览器对缓存数据的容量限度可能不太一样(某些浏览器设置的限度是每个站点 5MB)。
(2)如果 manifest 文件,或者外部列举的某一个文件不能失常下载,整个更新过程都将失败,浏览器持续全副应用老的缓存。
(3)援用 manifest 的 html 必须与 manifest 文件同源,在同一个域下。
(4)FALLBACK 中的资源必须和 manifest 文件同源。
(5)当一个资源被缓存后,该浏览器间接申请这个绝对路径也会拜访缓存中的资源。
(6)站点中的其余页面即便没有设置 manifest 属性,申请的资源如果在缓存中也从缓存中拜访。
(7)当 manifest 文件产生扭转时,资源申请自身也会触发更新。
AJAX
const getJSON = function(url) { return new Promise((resolve, reject) => { const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'); xhr.open('GET', url, false); xhr.setRequestHeader('Accept', 'application/json'); xhr.onreadystatechange = function() { if (xhr.readyState !== 4) return; if (xhr.status === 200 || xhr.status === 304) { resolve(xhr.responseText); } else { reject(new Error(xhr.responseText)); } } xhr.send(); })}
实现数组原型办法
forEach
Array.prototype.forEach2 = function(callback, thisArg) { if (this == null) { throw new TypeError('this is null or not defined') } if (typeof callback !== "function") { throw new TypeError(callback + ' is not a function') } const O = Object(this) // this 就是以后的数组 const len = O.length >>> 0 // 前面有解释 let k = 0 while (k < len) { if (k in O) { callback.call(thisArg, O[k], k, O); } k++; }}
O.length >>> 0 是什么操作?就是无符号右移 0 位,那有什么意义嘛?就是为了保障转换后的值为正整数。其实底层做了 2 层转换,第一是非 number 转成 number 类型,第二是将 number 转成 Uint32 类型
map
基于 forEach 的实现可能很容易写出 map 的实现:
- Array.prototype.forEach2 = function(callback, thisArg) {+ Array.prototype.map2 = function(callback, thisArg) { if (this == null) { throw new TypeError('this is null or not defined') } if (typeof callback !== "function") { throw new TypeError(callback + ' is not a function') } const O = Object(this) const len = O.length >>> 0- let k = 0+ let k = 0, res = [] while (k < len) { if (k in O) {- callback.call(thisArg, O[k], k, O);+ res[k] = callback.call(thisArg, O[k], k, O); } k++; }+ return res}
filter
同样,基于 forEach 的实现可能很容易写出 filter 的实现:
- Array.prototype.forEach2 = function(callback, thisArg) {+ Array.prototype.filter2 = function(callback, thisArg) { if (this == null) { throw new TypeError('this is null or not defined') } if (typeof callback !== "function") { throw new TypeError(callback + ' is not a function') } const O = Object(this) const len = O.length >>> 0- let k = 0+ let k = 0, res = [] while (k < len) { if (k in O) {- callback.call(thisArg, O[k], k, O);+ if (callback.call(thisArg, O[k], k, O)) {+ res.push(O[k]) + } } k++; }+ return res}
some
同样,基于 forEach 的实现可能很容易写出 some 的实现:
- Array.prototype.forEach2 = function(callback, thisArg) {+ Array.prototype.some2 = function(callback, thisArg) { if (this == null) { throw new TypeError('this is null or not defined') } if (typeof callback !== "function") { throw new TypeError(callback + ' is not a function') } const O = Object(this) const len = O.length >>> 0 let k = 0 while (k < len) { if (k in O) {- callback.call(thisArg, O[k], k, O);+ if (callback.call(thisArg, O[k], k, O)) {+ return true+ } } k++; }+ return false}
reduce
Array.prototype.reduce2 = function(callback, initialValue) { if (this == null) { throw new TypeError('this is null or not defined') } if (typeof callback !== "function") { throw new TypeError(callback + ' is not a function') } const O = Object(this) const len = O.length >>> 0 let k = 0, acc if (arguments.length > 1) { acc = initialValue } else { // 没传入初始值的时候,取数组中第一个非 empty 的值为初始值 while (k < len && !(k in O)) { k++ } if (k > len) { throw new TypeError( 'Reduce of empty array with no initial value' ); } acc = O[k++] } while (k < len) { if (k in O) { acc = callback(acc, O[k], k, O) } k++ } return acc}
如何防止回流与重绘?
缩小回流与重绘的措施:
- 操作DOM时,尽量在低层级的DOM节点进行操作
- 不要应用
table
布局, 一个小的改变可能会使整个table
进行从新布局 - 应用CSS的表达式
- 不要频繁操作元素的款式,对于动态页面,能够批改类名,而不是款式。
- 应用absolute或者fixed,使元素脱离文档流,这样他们发生变化就不会影响其余元素
- 防止频繁操作DOM,能够创立一个文档片段
documentFragment
,在它下面利用所有DOM操作,最初再把它增加到文档中 - 将元素先设置
display: none
,操作完结后再把它显示进去。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。 - 将DOM的多个读操作(或者写操作)放在一起,而不是读写操作穿插着写。这得益于浏览器的渲染队列机制。
浏览器针对页面的回流与重绘,进行了本身的优化——渲染队列
浏览器会将所有的回流、重绘的操作放在一个队列中,当队列中的操作到了肯定的数量或者到了肯定的工夫距离,浏览器就会对队列进行批处理。这样就会让屡次的回流、重绘变成一次回流重绘。
下面,将多个读操作(或者写操作)放在一起,就会等所有的读操作进入队列之后执行,这样,本来应该是触发屡次回流,变成了只触发一次回流。
说说浏览器缓存
缓存能够缩小网络 IO 耗费,进步访问速度。浏览器缓存是一种操作简略、效果显著的前端性能优化伎俩很多时候,大家偏向于将浏览器缓存简略地了解为“HTTP 缓存”。但事实上,浏览器缓存机制有四个方面,它们依照获取资源时申请的优先级顺次排列如下:Memory CacheService Worker CacheHTTP CachePush Cache缓存它又分为强缓存和协商缓存。优先级较高的是强缓存,在命中强缓存失败的状况下,才会走协商缓存 实现强缓存,过来咱们始终用 expires。 当服务器返回响应时,在 Response Headers 中将过期工夫写入 expires 字段,当初个别应用Cache-Control 两者同时呈现应用Cache-Control 协商缓存,Last-Modified 是一个工夫戳,如果咱们启用了协商缓存,它会在首次申请时随着 Response Headers 返回:每次申请去判断这个工夫戳是否发生变化。 从而去决定你是304读取缓存还是给你返回最新的数据
函数柯里化
柯里化(currying) 指的是将一个多参数的函数拆分成一系列函数,每个拆分后的函数都只承受一个参数。
对于曾经柯里化后的函数来说,当接管的参数数量与原函数的形参数量雷同时,执行原函数; 当接管的参数数量小于原函数的形参数量时,返回一个函数用于接管残余的参数,直至接管的参数数量与形参数量统一,执行原函数。
公布订阅模式(事件总线)
形容:实现一个公布订阅模式,领有 on, emit, once, off
办法
class EventEmitter { constructor() { // 蕴含所有监听器函数的容器对象 // 内部结构: {msg1: [listener1, listener2], msg2: [listener3]} this.cache = {}; } // 实现订阅 on(name, callback) { if(this.cache[name]) { this.cache[name].push(callback); } else { this.cache[name] = [callback]; } } // 删除订阅 off(name, callback) { if(this.cache[name]) { this.cache[name] = this.cache[name].filter(item => item !== callback); } if(this.cache[name].length === 0) delete this.cache[name]; } // 只执行一次订阅事件 once(name, callback) { callback(); this.off(name, callback); } // 触发事件 emit(name, ...data) { if(this.cache[name]) { // 创立正本,如果回调函数内持续注册雷同事件,会造成死循环 let tasks = this.cache[name].slice(); for(let fn of tasks) { fn(...data); } } }}
如何解决逾越问题
(1)CORS
上面是MDN对于CORS的定义:
跨域资源共享(CORS) 是一种机制,它应用额定的 HTTP 头来通知浏览器 让运行在一个 origin (domain)上的Web利用被准许拜访来自不同源服务器上的指定的资源。当一个资源从与该资源自身所在的服务器不同的域、协定或端口申请一个资源时,资源会发动一个跨域HTTP 申请。
CORS须要浏览器和服务器同时反对,整个CORS过程都是浏览器实现的,无需用户参加。因而实现CORS的要害就是服务器,只有服务器实现了CORS申请,就能够跨源通信了。
浏览器将CORS分为简略申请和非简略申请:
简略申请不会触发CORS预检申请。若该申请满足以下两个条件,就能够看作是简略申请:
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
若不满足以上条件,就属于非简略申请了。
(1)简略申请过程:
对于简略申请,浏览器会间接收回CORS申请,它会在申请的头信息中减少一个Orign字段,该字段用来阐明本次申请来自哪个源(协定+端口+域名),服务器会依据这个值来决定是否批准这次申请。如果Orign指定的域名在许可范畴之内,服务器返回的响应就会多出以下信息头:
Access-Control-Allow-Origin: http://api.bob.com // 和Orign始终Access-Control-Allow-Credentials: true // 示意是否容许发送CookieAccess-Control-Expose-Headers: FooBar // 指定返回其余字段的值Content-Type: text/html; charset=utf-8 // 示意文档类型
如果Orign指定的域名不在许可范畴之内,服务器会返回一个失常的HTTP回应,浏览器发现没有下面的Access-Control-Allow-Origin头部信息,就晓得出错了。这个谬误无奈通过状态码辨认,因为返回的状态码可能是200。
在简略申请中,在服务器内,至多须要设置字段:Access-Control-Allow-Origin
(2)非简略申请过程
非简略申请是对服务器有特殊要求的申请,比方申请办法为DELETE或者PUT等。非简略申请的CORS申请会在正式通信之前进行一次HTTP查问申请,称为预检申请。
浏览器会询问服务器,以后所在的网页是否在服务器容许拜访的范畴内,以及能够应用哪些HTTP申请形式和头信息字段,只有失去必定的回复,才会进行正式的HTTP申请,否则就会报错。
预检申请应用的申请办法是OPTIONS,示意这个申请是来询问的。他的头信息中的关键字段是Orign,示意申请来自哪个源。除此之外,头信息中还包含两个字段:
- Access-Control-Request-Method:该字段是必须的,用来列出浏览器的CORS申请会用到哪些HTTP办法。
- Access-Control-Request-Headers: 该字段是一个逗号分隔的字符串,指定浏览器CORS申请会额定发送的头信息字段。
服务器在收到浏览器的预检申请之后,会依据头信息的三个字段来进行判断,如果返回的头信息在中有Access-Control-Allow-Origin这个字段就是容许跨域申请,如果没有,就是不批准这个预检申请,就会报错。
服务器回应的CORS的字段如下:
Access-Control-Allow-Origin: http://api.bob.com // 容许跨域的源地址Access-Control-Allow-Methods: GET, POST, PUT // 服务器反对的所有跨域申请的办法Access-Control-Allow-Headers: X-Custom-Header // 服务器反对的所有头信息字段Access-Control-Allow-Credentials: true // 示意是否容许发送CookieAccess-Control-Max-Age: 1728000 // 用来指定本次预检申请的有效期,单位为秒
只有服务器通过了预检申请,在当前每次的CORS申请都会自带一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。
在非简略申请中,至多须要设置以下字段:
'Access-Control-Allow-Origin' 'Access-Control-Allow-Methods''Access-Control-Allow-Headers'
缩小OPTIONS申请次数:
OPTIONS申请次数过多就会损耗页面加载的性能,升高用户体验度。所以尽量要缩小OPTIONS申请次数,能够后端在申请的返回头部增加:Access-Control-Max-Age:number。它示意预检申请的返回后果能够被缓存多久,单位是秒。该字段只对齐全一样的URL的缓存设置失效,所以设置了缓存工夫,在这个工夫范畴内,再次发送申请就不须要进行预检申请了。
CORS中Cookie相干问题:
在CORS申请中,如果想要传递Cookie,就要满足以下三个条件:
- 在申请中设置
withCredentials
默认状况下在跨域申请,浏览器是不带 cookie 的。然而咱们能够通过设置 withCredentials 来进行传递 cookie.
// 原生 xml 的设置形式var xhr = new XMLHttpRequest();xhr.withCredentials = true;// axios 设置形式axios.defaults.withCredentials = true;
- Access-Control-Allow-Credentials 设置为 true
- Access-Control-Allow-Origin 设置为非
*
(2)JSONP
jsonp的原理就是利用<script>
标签没有跨域限度,通过<script>
标签src属性,发送带有callback参数的GET申请,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。
1)原生JS实现:
<script> var script = document.createElement('script'); script.type = 'text/javascript'; // 传参一个回调函数名给后端,不便后端返回时执行这个在前端定义的回调函数 script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback'; document.head.appendChild(script); // 回调执行函数 function handleCallback(res) { alert(JSON.stringify(res)); } </script>
服务端返回如下(返回时即执行全局函数):
handleCallback({"success": true, "user": "admin"})
2)Vue axios实现:
this.$http = axios;this.$http.jsonp('http://www.domain2.com:8080/login', { params: {}, jsonp: 'handleCallback'}).then((res) => { console.log(res); })
后端node.js代码:
var querystring = require('querystring');var http = require('http');var server = http.createServer();server.on('request', function(req, res) { var params = querystring.parse(req.url.split('?')[1]); var fn = params.callback; // jsonp返回设置 res.writeHead(200, { 'Content-Type': 'text/javascript' }); res.write(fn + '(' + JSON.stringify(params) + ')'); res.end();});server.listen('8080');console.log('Server is running at port 8080...');
JSONP的毛病:
- 具备局限性, 仅反对get办法
- 不平安,可能会蒙受XSS攻打
(3)postMessage 跨域
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多能够跨域操作的window属性之一,它可用于解决以下方面的问题:
- 页面和其关上的新窗口的数据传递
- 多窗口之间消息传递
- 页面与嵌套的iframe消息传递
- 下面三个场景的跨域数据传递
用法:postMessage(data,origin)办法承受两个参数:
- data: html5标准反对任意根本类型或可复制的对象,但局部浏览器只反对字符串,所以传参时最好用JSON.stringify()序列化。
- origin: 协定+主机+端口号,也能够设置为"*",示意能够传递给任意窗口,如果要指定和以后窗口同源的话设置为"/"。
1)a.html:(domain1.com/a.html)
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe><script> var iframe = document.getElementById('iframe'); iframe.onload = function() { var data = { name: 'aym' }; // 向domain2传送跨域数据 iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com'); }; // 承受domain2返回数据 window.addEventListener('message', function(e) { alert('data from domain2 ---> ' + e.data); }, false);</script>
2)b.html:(domain2.com/b.html)
<script> // 接管domain1的数据 window.addEventListener('message', function(e) { alert('data from domain1 ---> ' + e.data); var data = JSON.parse(e.data); if (data) { data.number = 16; // 解决后再发回domain1 window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com'); } }, false);</script>
(4)nginx代理跨域
nginx代理跨域,本质和CORS跨域原理一样,通过配置文件设置申请响应头Access-Control-Allow-Origin…等字段。
1)nginx配置解决iconfont跨域
浏览器跨域拜访js、css、img等惯例动态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的动态资源服务器中退出以下配置。
location / { add_header Access-Control-Allow-Origin *;}
2)nginx反向代理接口跨域
跨域问题:同源策略仅是针对浏览器的安全策略。服务器端调用HTTP接口只是应用HTTP协定,不须要同源策略,也就不存在跨域问题。
实现思路:通过Nginx配置一个代理服务器域名与domain1雷同,端口不同)做跳板机,反向代理拜访domain2接口,并且能够顺便批改cookie中domain信息,不便以后域cookie写入,实现跨域拜访。
nginx具体配置:
#proxy服务器server { listen 81; server_name www.domain1.com; location / { proxy_pass http://www.domain2.com:8080; #反向代理 proxy_cookie_domain www.domain2.com www.domain1.com; #批改cookie里域名 index index.html index.htm; # 当用webpack-dev-server等中间件代理接口拜访nignx时,此时无浏览器参加,故没有同源限度,上面的跨域配置可不启用 add_header Access-Control-Allow-Origin http://www.domain1.com; #以后端只跨域不带cookie时,可为* add_header Access-Control-Allow-Credentials true; }}
(5)nodejs 中间件代理跨域
node中间件实现跨域代理,原理大抵与nginx雷同,都是通过启一个代理服务器,实现数据的转发,也能够通过设置cookieDomainRewrite参数批改响应头中cookie中域名,实现以后域的cookie写入,不便接口登录认证。
1)非vue框架的跨域 应用node + express + http-proxy-middleware搭建一个proxy服务器。
- 前端代码:
var xhr = new XMLHttpRequest();// 前端开关:浏览器是否读写cookiexhr.withCredentials = true;// 拜访http-proxy-middleware代理服务器xhr.open('get', 'http://www.domain1.com:3000/login?user=admin', true);xhr.send();
- 中间件服务器代码:
var express = require('express');var proxy = require('http-proxy-middleware');var app = express();app.use('/', proxy({ // 代理跨域指标接口 target: 'http://www.domain2.com:8080', changeOrigin: true, // 批改响应头信息,实现跨域并容许带cookie onProxyRes: function(proxyRes, req, res) { res.header('Access-Control-Allow-Origin', 'http://www.domain1.com'); res.header('Access-Control-Allow-Credentials', 'true'); }, // 批改响应信息中的cookie域名 cookieDomainRewrite: 'www.domain1.com' // 能够为false,示意不批改}));app.listen(3000);console.log('Proxy server is listen at port 3000...');
2)vue框架的跨域
node + vue + webpack + webpack-dev-server搭建的我的项目,跨域申请接口,间接批改webpack.config.js配置。开发环境下,vue渲染服务和接口代理服务都是webpack-dev-server同一个,所以页面与代理接口之间不再跨域。
webpack.config.js局部配置:
module.exports = { entry: {}, module: {}, ... devServer: { historyApiFallback: true, proxy: [{ context: '/login', target: 'http://www.domain2.com:8080', // 代理跨域指标接口 changeOrigin: true, secure: false, // 当代理某些https服务报错时用 cookieDomainRewrite: 'www.domain1.com' // 能够为false,示意不批改 }], noInfo: true }}
(6)document.domain + iframe跨域
此计划仅限主域雷同,子域不同的跨域利用场景。实现原理:两个页面都通过js强制设置document.domain为根底主域,就实现了同域。
1)父窗口:(domain.com/a.html)
<iframe id="iframe" src="http://child.domain.com/b.html"></iframe><script> document.domain = 'domain.com'; var user = 'admin';</script>
1)子窗口:(child.domain.com/a.html)
<script> document.domain = 'domain.com'; // 获取父窗口中变量 console.log('get js data from parent ---> ' + window.parent.user);</script>
(7)location.hash + iframe跨域
实现原理:a欲与b跨域互相通信,通过两头页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,雷同域之间间接js拜访来通信。
具体实现:A域:a.html -> B域:b.html -> A域:c.html,a与b不同域只能通过hash值单向通信,b与c也不同域也只能单向通信,但c与a同域,所以c可通过parent.parent拜访a页面所有对象。
1)a.html:(domain1.com/a.html)
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe><script> var iframe = document.getElementById('iframe'); // 向b.html传hash值 setTimeout(function() { iframe.src = iframe.src + '#user=admin'; }, 1000); // 凋谢给同域c.html的回调办法 function onCallback(res) { alert('data from c.html ---> ' + res); }</script>
2)b.html:(.domain2.com/b.html)
<iframe id="iframe" src="http://www.domain1.com/c.html" style="display:none;"></iframe><script> var iframe = document.getElementById('iframe'); // 监听a.html传来的hash值,再传给c.html window.onhashchange = function () { iframe.src = iframe.src + location.hash; };</script>
<script> // 监听b.html传来的hash值 window.onhashchange = function () { // 再通过操作同域a.html的js回调,将后果传回 window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', '')); };</script>
(8)window.name + iframe跨域
window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后仍旧存在,并且能够反对十分长的 name 值(2MB)。
1)a.html:(domain1.com/a.html)
var proxy = function(url, callback) { var state = 0; var iframe = document.createElement('iframe'); // 加载跨域页面 iframe.src = url; // onload事件会触发2次,第1次加载跨域页,并留存数据于window.name iframe.onload = function() { if (state === 1) { // 第2次onload(同域proxy页)胜利后,读取同域window.name中数据 callback(iframe.contentWindow.name); destoryFrame(); } else if (state === 0) { // 第1次onload(跨域页)胜利后,切换到同域代理页面 iframe.contentWindow.location = 'http://www.domain1.com/proxy.html'; state = 1; } }; document.body.appendChild(iframe); // 获取数据当前销毁这个iframe,开释内存;这也保障了平安(不被其余域frame js拜访) function destoryFrame() { iframe.contentWindow.document.write(''); iframe.contentWindow.close(); document.body.removeChild(iframe); }};// 申请跨域b页面数据proxy('http://www.domain2.com/b.html', function(data){ alert(data);});
2)proxy.html:(domain1.com/proxy.html)
两头代理页,与a.html同域,内容为空即可。
3)b.html:(domain2.com/b.html)
<script> window.name = 'This is domain2 data!';</script>
通过iframe的src属性由外域转向本地区,跨域数据即由iframe的window.name从外域传递到本地区。这个就奇妙地绕过了浏览器的跨域拜访限度,但同时它又是平安操作。
(9)WebSocket协定跨域
WebSocket protocol是HTML5一种新的协定。它实现了浏览器与服务器全双工通信,同时容许跨域通信,是server push技术的一种很好的实现。
原生WebSocket API应用起来不太不便,咱们应用Socket.io,它很好地封装了webSocket接口,提供了更简略、灵便的接口,也对不反对webSocket的浏览器提供了向下兼容。
1)前端代码:
<div>user input:<input type="text"></div><script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.js"></script><script>var socket = io('http://www.domain2.com:8080');// 连贯胜利解决socket.on('connect', function() { // 监听服务端音讯 socket.on('message', function(msg) { console.log('data from server: ---> ' + msg); }); // 监听服务端敞开 socket.on('disconnect', function() { console.log('Server socket has closed.'); });});document.getElementsByTagName('input')[0].onblur = function() { socket.send(this.value);};</script>
2)Nodejs socket后盾:
var http = require('http');var socket = require('socket.io');// 启http服务var server = http.createServer(function(req, res) { res.writeHead(200, { 'Content-type': 'text/html' }); res.end();});server.listen('8080');console.log('Server is running at port 8080...');// 监听socket连贯socket.listen(server).on('connection', function(client) { // 接管信息 client.on('message', function(msg) { client.send('hello:' + msg); console.log('data from client: ---> ' + msg); }); // 断开解决 client.on('disconnect', function() { console.log('Client socket has closed.'); });});
懒加载的概念
懒加载也叫做提早加载、按需加载,指的是在长网页中提早加载图片数据,是一种较好的网页性能优化的形式。在比拟长的网页或利用中,如果图片很多,所有的图片都被加载进去,而用户只能看到可视窗口的那一部分图片数据,这样就节约了性能。
如果应用图片的懒加载就能够解决以上问题。在滚动屏幕之前,可视化区域之外的图片不会进行加载,在滚动屏幕时才加载。这样使得网页的加载速度更快,缩小了服务器的负载。懒加载实用于图片较多,页面列表较长(长列表)的场景中。
介绍下 promise 的个性、优缺点,外部是如何实现的,入手实现 Promise
1)Promise根本个性
- 1、Promise有三种状态:pending(进行中)、fulfilled(已胜利)、rejected(已失败)
- 2、Promise对象承受一个回调函数作为参数, 该回调函数承受两个参数,别离是胜利时的回调resolve和失败时的回调reject;另外resolve的参数除了正常值以外, 还可能是一个Promise对象的实例;reject的参数通常是一个Error对象的实例。
- 3、then办法返回一个新的Promise实例,并接管两个参数onResolved(fulfilled状态的回调);onRejected(rejected状态的回调,该参数可选)
- 4、catch办法返回一个新的Promise实例
- 5、finally办法不论Promise状态如何都会执行,该办法的回调函数不承受任何参数
- 6、Promise.all()办法将多个多个Promise实例,包装成一个新的Promise实例,该办法承受一个由Promise对象组成的数组作为参数(Promise.all()办法的参数能够不是数组,但必须具备Iterator接口,且返回的每个成员都是Promise实例),留神参数中只有有一个实例触发catch办法,都会触发Promise.all()办法返回的新的实例的catch办法,如果参数中的某个实例自身调用了catch办法,将不会触发Promise.all()办法返回的新实例的catch办法
- 7、Promise.race()办法的参数与Promise.all办法一样,参数中的实例只有有一个率先扭转状态就会将该实例的状态传给Promise.race()办法,并将返回值作为Promise.race()办法产生的Promise实例的返回值
- 8、Promise.resolve()将现有对象转为Promise对象,如果该办法的参数为一个Promise对象,Promise.resolve()将不做任何解决;如果参数thenable对象(即具备then办法),Promise.resolve()将该对象转为Promise对象并立刻执行then办法;如果参数是一个原始值,或者是一个不具备then办法的对象,则Promise.resolve办法返回一个新的Promise对象,状态为fulfilled,其参数将会作为then办法中onResolved回调函数的参数,如果Promise.resolve办法不带参数,会间接返回一个fulfilled状态的 Promise 对象。须要留神的是,立刻resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的完结时执行,而不是在下一轮“事件循环”的开始时。
- 9、Promise.reject()同样返回一个新的Promise对象,状态为rejected,无论传入任何参数都将作为reject()的参数
2)Promise长处
①对立异步 API
- Promise 的一个重要长处是它将逐步被用作浏览器的异步 API ,对立当初各种各样的 API ,以及不兼容的模式和手法。
②Promise 与事件比照
- 和事件相比拟, Promise 更适宜解决一次性的后果。在后果计算出来之前或之后注册回调函数都是能够的,都能够拿到正确的值。 Promise 的这个长处很天然。然而,不能应用 Promise 解决屡次触发的事件。链式解决是 Promise 的又一长处,然而事件却不能这样链式解决。
③Promise 与回调比照
- 解决了回调天堂的问题,将异步操作以同步操作的流程表达出来。
- ④Promise 带来的额定益处是蕴含了更好的错误处理形式(蕴含了异样解决),并且写起来很轻松(因为能够重用一些同步的工具,比方 Array.prototype.map() )。
3)Promise毛病
- 1、无奈勾销Promise,一旦新建它就会立刻执行,无奈中途勾销。
- 2、如果不设置回调函数,Promise外部抛出的谬误,不会反馈到内部。
- 3、当处于Pending状态时,无奈得悉目前停顿到哪一个阶段(刚刚开始还是行将实现)。
- 4、Promise 真正执行回调的时候,定义 Promise 那局部实际上曾经走完了,所以 Promise 的报错堆栈上下文不太敌对。
4)简略代码实现
最简略的Promise实现有7个次要属性, state(状态), value(胜利返回值), reason(错误信息), resolve办法, reject办法, then办法
class Promise{ constructor(executor) { this.state = 'pending'; this.value = undefined; this.reason = undefined; let resolve = value => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; } }; let reject = reason => { if (this.state === 'pending') { this.state = 'rejected'; this.reason = reason; } }; try { // 立刻执行函数 executor(resolve, reject); } catch (err) { reject(err); } } then(onFulfilled, onRejected) { if (this.state === 'fulfilled') { let x = onFulfilled(this.value); }; if (this.state === 'rejected') { let x = onRejected(this.reason); }; }}
5)面试够用版
function myPromise(constructor){ let self=this; self.status="pending" //定义状态扭转前的初始状态 self.value=undefined;//定义状态为resolved的时候的状态 self.reason=undefined;//定义状态为rejected的时候的状态 function resolve(value){ //两个==="pending",保障了了状态的扭转是不不可逆的 if(self.status==="pending"){ self.value=value; self.status="resolved"; } } function reject(reason){ //两个==="pending",保障了了状态的扭转是不不可逆的 if(self.status==="pending"){ self.reason=reason; self.status="rejected"; } } //捕捉结构异样 try{ constructor(resolve,reject); }catch(e){ reject(e); } }myPromise.prototype.then=function(onFullfilled,onRejected){ let self=this; switch(self.status){ case "resolved": onFullfilled(self.value); break; case "rejected": onRejected(self.reason); break; default: }}// 测试var p=new myPromise(function(resolve,reject){resolve(1)}); p.then(function(x){console.log(x)})//输入1
6)大厂专供版
const PENDING = "pending"; const FULFILLED = "fulfilled"; const REJECTED = "rejected";const resolvePromise = (promise, x, resolve, reject) => { if (x === promise) { // If promise and x refer to the same object, reject promise with a TypeError as the reason. reject(new TypeError('循环援用')) } // if x is an object or function, if (x !== null && typeof x === 'object' || typeof x === 'function') { // If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored. let called try { // If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason. let then = x.then // Let then be x.then // If then is a function, call it with x as this if (typeof then === 'function') { // If/when resolvePromise is called with a value y, run [[Resolve]](promise, y) // If/when rejectPromise is called with a reason r, reject promise with r. then.call(x, y => { if (called) return called = true resolvePromise(promise, y, resolve, reject) }, r => { if (called) return called = true reject(r) }) } else { // If then is not a function, fulfill promise with x. resolve(x) } } catch (e) { if (called) return called = true reject(e) } } else { // If x is not an object or function, fulfill promise with x resolve(x) }}function Promise(excutor) { let that = this; // 缓存以后promise实例例对象 that.status = PENDING; // 初始状态 that.value = undefined; // fulfilled状态时 返回的信息 that.reason = undefined; // rejected状态时 回绝的起因 that.onFulfilledCallbacks = []; // 存储fulfilled状态对应的onFulfilled函数 that.onRejectedCallbacks = []; // 存储rejected状态对应的onRejected函数 function resolve(value) { // value胜利态时接管的终值 if(value instanceof Promise) { return value.then(resolve, reject); } // 实际中要确保 onFulfilled 和 onRejected ⽅办法异步执⾏行行,且应该在 then ⽅办法被调⽤用的那⼀一轮事件循环之后的新执⾏行行栈中执⾏行行。 setTimeout(() => { // 调⽤用resolve 回调对应onFulfilled函数 if (that.status === PENDING) { // 只能由pending状态 => fulfilled状态 (防止调⽤用屡次resolve reject) that.status = FULFILLED; that.value = value; that.onFulfilledCallbacks.forEach(cb => cb(that.value)); } }); } function reject(reason) { // reason失败态时接管的拒因 setTimeout(() => { // 调⽤用reject 回调对应onRejected函数 if (that.status === PENDING) { // 只能由pending状态 => rejected状态 (防止调⽤用屡次resolve reject) that.status = REJECTED; that.reason = reason; that.onRejectedCallbacks.forEach(cb => cb(that.reason)); } }); } // 捕捉在excutor执⾏行行器器中抛出的异样 // new Promise((resolve, reject) => { // throw new Error('error in excutor') // }) try { excutor(resolve, reject); } catch (e) { reject(e); }}Promise.prototype.then = function(onFulfilled, onRejected) { const that = this; let newPromise; // 解决理参数默认值 保障参数后续可能持续执⾏行行 onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value; onRejected = typeof onRejected === "function" ? onRejected : reason => { throw reason; }; if (that.status === FULFILLED) { // 胜利态 return newPromise = new Promise((resolve, reject) => { setTimeout(() => { try{ let x = onFulfilled(that.value); resolvePromise(newPromise, x, resolve, reject); //新的promise resolve 上⼀一个onFulfilled的返回值 } catch(e) { reject(e); // 捕捉前⾯面onFulfilled中抛出的异样then(onFulfilled, onRejected); } }); }) } if (that.status === REJECTED) { // 失败态 return newPromise = new Promise((resolve, reject) => { setTimeout(() => { try { let x = onRejected(that.reason); resolvePromise(newPromise, x, resolve, reject); } catch(e) { reject(e); } }); }); } if (that.status === PENDING) { // 期待态// 当异步调⽤用resolve/rejected时 将onFulfilled/onRejected收集暂存到汇合中 return newPromise = new Promise((resolve, reject) => { that.onFulfilledCallbacks.push((value) => { try { let x = onFulfilled(value); resolvePromise(newPromise, x, resolve, reject); } catch(e) { reject(e); } }); that.onRejectedCallbacks.push((reason) => { try { let x = onRejected(reason); resolvePromise(newPromise, x, resolve, reject); } catch(e) { reject(e); } }); }); }};
说一说SessionStorage和localStorage还有cookie
共同点:都是保留在浏览器端、且同源的不同点: 1.cookie数据始终在同源的http申请中携带(即便不须要),即cookie在浏览器和服务器间来回传递。 cookie数据还有门路(path)的概念,能够限度cookie只属于某个门路下 sessionStorage和localStorage不会主动把数据发送给服务器,仅在本地保留。 2.存储大小限度也不同,cookie数据不能超过4K,sessionStorage和localStorage能够达到5M 3.sessionStorage:仅在以后浏览器窗口敞开之前无效; localStorage:始终无效,窗口或浏览器敞开也始终保留,本地存储,因而用作持久数据; cookie:只在设置的cookie过期工夫之前无效,即便窗口敞开或浏览器敞开 4.作用域不同 sessionStorage:不在不同的浏览器窗口中共享,即便是同一个页面; localstorage:在所有同源窗口中都是共享的;也就是说只有浏览器不敞开,数据依然存在 cookie: 也是在所有同源窗口中都是共享的.也就是说只有浏览器不敞开,数据依然存在
事件流
事件流是网页元素接管事件的程序,"DOM2级事件"规定的事件流包含三个阶段:事件捕捉阶段、处于指标阶段、事件冒泡阶段。
首先产生的事件捕捉,为截获事件提供机会。而后是理论的指标承受事件。最初一个阶段是工夫冒泡阶段,能够在这个阶段对事件做出响应。
尽管捕捉阶段在标准中规定不容许响应事件,然而实际上还是会执行,所以有两次机会获取到指标对象。
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>事件冒泡</title></head><body> <div> <p id="parEle">我是父元素 <span id="sonEle">我是子元素</span></p> </div></body></html><script type="text/javascript">var sonEle = document.getElementById('sonEle');var parEle = document.getElementById('parEle');parEle.addEventListener('click', function () { alert('父级 冒泡');}, false);parEle.addEventListener('click', function () { alert('父级 捕捉');}, true);sonEle.addEventListener('click', function () { alert('子级冒泡');}, false);sonEle.addEventListener('click', function () { alert('子级捕捉');}, true);</script>
当容器元素及嵌套元素,即在捕捉阶段
又在冒泡阶段
调用事件处理程序时:事件按DOM事件流的程序执行事件处理程序:
- 父级捕捉
- 子级捕捉
- 子级冒泡
- 父级冒泡
且当事件处于指标阶段时,事件调用程序决定于绑定事件的书写程序,按下面的例子为,先调用冒泡阶段的事件处理程序,再调用捕捉阶段的事件处理程序。顺次alert出“子集冒泡”,“子集捕捉”。
代码输入后果
function Dog() { this.name = 'puppy'}Dog.prototype.bark = () => { console.log('woof!woof!')}const dog = new Dog()console.log(Dog.prototype.constructor === Dog && dog.constructor === Dog && dog instanceof Dog)
输入后果:true
解析: 因为constructor是prototype上的属性,所以dog.constructor实际上就是指向Dog.prototype.constructor;constructor属性指向构造函数。instanceof而理论检测的是类型是否在实例的原型链上。
constructor是prototype上的属性,这一点很容易被疏忽掉。constructor和instanceof 的作用是不同的,理性地来说,constructor的限度比拟严格,它只能严格比照对象的构造函数是不是指定的值;而instanceof比拟涣散,只有检测的类型在原型链上,就会返回true。
说一说js是什么语言
JavaScript是一种直译式脚本语言,是一种动静类型、弱类型、基于原型的语言,内置反对类型。它的解释器被称为JavaScript引擎,为浏览器的一部分,宽泛用于客户端的脚本语言,最早是在HTML(规范通用标记语言下的一个利用)网页上应用,用来给HTML网页减少动静性能。js语言是弱语言类型, 因而咱们在我的项目开发中当咱们随便更该某个变量的数据类型后有可能会导致其余援用这个变量的办法中报错等等。
实现节流函数和防抖函数
函数防抖的实现:
function debounce(fn, wait) { var timer = null; return function() { var context = this, args = [...arguments]; // 如果此时存在定时器的话,则勾销之前的定时器从新记时 if (timer) { clearTimeout(timer); timer = null; } // 设置定时器,使事件间隔指定事件后执行 timer = setTimeout(() => { fn.apply(context, args); }, wait); };}
函数节流的实现:
// 工夫戳版function throttle(fn, delay) { var preTime = Date.now(); return function() { var context = this, args = [...arguments], nowTime = Date.now(); // 如果两次工夫距离超过了指定工夫,则执行函数。 if (nowTime - preTime >= delay) { preTime = Date.now(); return fn.apply(context, args); } };}// 定时器版function throttle (fun, wait){ let timeout = null return function(){ let context = this let args = [...arguments] if(!timeout){ timeout = setTimeout(() => { fun.apply(context, args) timeout = null }, wait) } }}