乐趣区

关于前端:腾讯前端一面常考面试题

如何依据设计稿进行挪动端适配?

挪动端适配次要有两个维度:

  • 适配不同像素密度, 针对不同的像素密度,应用 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

解析:

  1. console.log(new A().a),new A()为构造函数创立的对象,自身没有 a 属性,所以向它的原型去找,发现原型的 a 属性的属性值为 1,故该输入值为 1;
  2. console.log(new B().a),ew B()为构造函数创立的对象,该构造函数有参数 a,但该对象没有传参,故该输入值为 undefined;
  3. 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 Cache
Service Worker Cache
HTTP Cache
Push 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   // 示意是否容许发送 Cookie
Access-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   // 示意是否容许发送 Cookie
Access-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();
// 前端开关:浏览器是否读写 cookie
xhr.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)
    }
  }
}
退出移动版