百度前端二面常考面试题
创始人
2024-03-21 14:51:08

HTTP分层

  • 第一层:物理层,TCP/IP 里无对应;
  • 第二层:数据链路层,对应 TCP/IP 的链接层;
  • 第三层:网络层,对应 TCP/IP 的网际层;
  • 第四层:传输层,对应 TCP/IP 的传输层;
  • 第五、六、七层:统一对应到 TCP/IP 的应用层

总结

  • TCP/IP 分为四层,核心是二层的 IP 和三层的 TCPHTTP 在第四层;
  • OSI 分为七层,基本对应 TCP/IPTCP 在第四层,HTTP 在第七层;
  • OSI 可以映射到 TCP/IP,但这期间一、五、六层消失了;
  • 日常交流的时候我们通常使用 OSI 模型,用四层、七层等术语;
  • HTTP 利用 TCP/IP协议栈逐层打包再拆包,实现了数据传输,但下面的细节并不可见

有一个辨别四层和七层比较好的(但不是绝对的)小窍门,“两个凡是”:凡是由操作系统负责处理的就是四层或四层以下,否则,凡是需要由应用程序(也就是你自己写代码)负责处理的就是七层

什么是 XSS 攻击?

(1)概念

XSS 攻击指的是跨站脚本攻击,是一种代码注入攻击。攻击者通过在网站注入恶意脚本,使之在用户的浏览器上运行,从而盗取用户的信息如 cookie 等。

XSS 的本质是因为网站没有对恶意代码进行过滤,与正常的代码混合在一起了,浏览器没有办法分辨哪些脚本是可信的,从而导致了恶意代码的执行。

攻击者可以通过这种攻击方式可以进行以下操作:

  • 获取页面的数据,如DOM、cookie、localStorage;
  • DOS攻击,发送合理请求,占用服务器资源,从而使用户无法访问服务器;
  • 破坏页面结构;
  • 流量劫持(将链接指向某网站);

(2)攻击类型

XSS 可以分为存储型、反射型和 DOM 型:

  • 存储型指的是恶意脚本会存储在目标服务器上,当浏览器请求数据时,脚本从服务器传回并执行。
  • 反射型指的是攻击者诱导用户访问一个带有恶意代码的 URL 后,服务器端接收数据后处理,然后把带有恶意代码的数据发送到浏览器端,浏览器端解析这段带有 XSS 代码的数据后当做脚本执行,最终完成 XSS 攻击。
  • DOM 型指的通过修改页面的 DOM 节点形成的 XSS。

1)存储型 XSS 的攻击步骤:

  1. 攻击者将恶意代码提交到⽬标⽹站的数据库中。
  2. ⽤户打开⽬标⽹站时,⽹站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器。
  3. ⽤户浏览器接收到响应后解析执⾏,混在其中的恶意代码也被执⾏。
  4. 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者冒充⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。

这种攻击常⻅于带有⽤户保存数据的⽹站功能,如论坛发帖、商品评论、⽤户私信等。

2)反射型 XSS 的攻击步骤:

  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  2. ⽤户打开带有恶意代码的 URL 时,⽹站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
  3. ⽤户浏览器接收到响应后解析执⾏,混在其中的恶意代码也被执⾏。
  4. 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者冒充⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。

反射型 XSS 跟存储型 XSS 的区别是:存储型 XSS 的恶意代码存在数据库⾥,反射型 XSS 的恶意代码存在 URL ⾥。

反射型 XSS 漏洞常⻅于通过 URL 传递参数的功能,如⽹站搜索、跳转等。 由于需要⽤户主动打开恶意的 URL 才能⽣效,攻击者往往会结合多种⼿段诱导⽤户点击。

3)DOM 型 XSS 的攻击步骤:

  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  2. ⽤户打开带有恶意代码的 URL。
  3. ⽤户浏览器接收到响应后解析执⾏,前端 JavaScript 取出 URL 中的恶意代码并执⾏。
  4. 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者冒充⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。

DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻击中,取出和执⾏恶意代码由浏览器端完成,属于前端JavaScript ⾃身的安全漏洞,⽽其他两种 XSS 都属于服务端的安全漏洞。

手写 bind、apply、call

// callFunction.prototype.call = function (context, ...args) {context = context || window;const fnSymbol = Symbol("fn");context[fnSymbol] = this;context[fnSymbol](...args);delete context[fnSymbol];
}
// applyFunction.prototype.apply = function (context, argsArr) {context = context || window;const fnSymbol = Symbol("fn");context[fnSymbol] = this;context[fnSymbol](...argsArr);delete context[fnSymbol];
}
// bindFunction.prototype.bind = function (context, ...args) {context = context || window;const fnSymbol = Symbol("fn");context[fnSymbol] = this;return function (..._args) {args = args.concat(_args);context[fnSymbol](...args);delete context[fnSymbol];   }
}

首屏和白屏时间如何计算

首屏时间的计算,可以由 Native WebView 提供的类似 onload 的方法实现,在 ios 下对应的是 webViewDidFinishLoad,在 android 下对应的是onPageFinished事件。

白屏的定义有多种。可以认为“没有任何内容”是白屏,可以认为“网络或服务异常”是白屏,可以认为“数据加载中”是白屏,可以认为“图片加载不出来”是白屏。场景不同,白屏的计算方式就不相同。

方法1:当页面的元素数小于x时,则认为页面白屏。比如“没有任何内容”,可以获取页面的DOM节点数,判断DOM节点数少于某个阈值X,则认为白屏。 方法2:当页面出现业务定义的错误码时,则认为是白屏。比如“网络或服务异常”。 方法3:当页面出现业务定义的特征值时,则认为是白屏。比如“数据加载中”。

介绍下 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 calledtry { // 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 thisif (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) returncalled = trueresolvePromise(promise, y, resolve, reject)}, r => {if (called) returncalled = truereject(r)})} else {// If then is not a function, fulfill promise with x.resolve(x)}} catch (e) {if (called) returncalled = truereject(e)}} else {// If x is not an object or function, fulfill promise with xresolve(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);}});});}
};

死锁产生的原因? 如果解决死锁的问题?

所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。

系统中的资源可以分为两类:

  • 可剥夺资源,是指某进程在获得这类资源后,该资源可以再被其他进程或系统剥夺,CPU和主存均属于可剥夺性资源;
  • 不可剥夺资源,当系统把这类资源分配给某进程后,再不能强行收回,只能在进程用完后自行释放,如磁带机、打印机等。

产生死锁的原因:

(1)竞争资源

  • 产生死锁中的竞争资源之一指的是竞争不可剥夺资源(例如:系统中只有一台打印机,可供进程P1使用,假定P1已占用了打印机,若P2继续要求打印机打印将阻塞)
  • 产生死锁中的竞争资源另外一种资源指的是竞争临时资源(临时资源包括硬件中断、信号、消息、缓冲区内的消息等),通常消息通信顺序进行不当,则会产生死锁

(2)进程间推进顺序非法

若P1保持了资源R1,P2保持了资源R2,系统处于不安全状态,因为这两个进程再向前推进,便可能发生死锁。例如,当P1运行到P1:Request(R2)时,将因R2已被P2占用而阻塞;当P2运行到P2:Request(R1)时,也将因R1已被P1占用而阻塞,于是发生进程死锁

产生死锁的必要条件:

  • 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
  • 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
  • 环路等待条件:在发生死锁时,必然存在一个进程——资源的环形链。

预防死锁的方法:

  • 资源一次性分配:一次性分配所有资源,这样就不会再有请求了(破坏请求条件)
  • 只要有一个资源得不到分配,也不给这个进程分配其他的资源(破坏请保持条件)
  • 可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
  • 资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)

参考:前端进阶面试题详细解答

选择排序–时间复杂度 n^2

题目描述:实现一个选择排序

实现代码如下:

function selectSort(arr) {// 缓存数组长度const len = arr.length;// 定义 minIndex,缓存当前区间最小值的索引,注意是索引let minIndex;// i 是当前排序区间的起点for (let i = 0; i < len - 1; i++) {// 初始化 minIndex 为当前区间第一个元素minIndex = i;// i、j分别定义当前区间的上下界,i是左边界,j是右边界for (let j = i; j < len; j++) {// 若 j 处的数据项比当前最小值还要小,则更新最小值索引为 jif (arr[j] < arr[minIndex]) {minIndex = j;}}// 如果 minIndex 对应元素不是目前的头部元素,则交换两者if (minIndex !== i) {[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];}}return arr;
}
// console.log(quickSort([3, 6, 2, 4, 1]));

浏览器渲染优化

(1)针对JavaScript: JavaScript既会阻塞HTML的解析,也会阻塞CSS的解析。因此我们可以对JavaScript的加载方式进行改变,来进行优化:

(1)尽量将JavaScript文件放在body的最后

(2) body中间尽量不要写

服务端返回如下(返回时即执行全局函数):

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)


2)b.html:(domain2.com/b.html)

(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,// 修改响应头信息,实现跨域并允许带cookieonProxyRes: 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)


1)子窗口:(child.domain.com/a.html)

(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)


2)b.html:(.domain2.com/b.html)


(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.nameiframe.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)

通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。

(9)WebSocket协议跨域

WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。

原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。

1)前端代码:

user input:

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.'); });
});

同步和异步的区别

  • 同步指的是当一个进程在执行某个请求时,如果这个请求需要等待一段时间才能返回,那么这个进程会一直等待下去,直到消息返回为止再继续向下执行。
  • 异步指的是当一个进程在执行某个请求时,如果这个请求需要等待一段时间才能返回,这个时候进程会继续往下执行,不会阻塞等待消息的返回,当消息返回时系统再通知进程进行处理。

什么是文档的预解析?

Webkit 和 Firefox 都做了这个优化,当执行 JavaScript 脚本时,另一个线程解析剩下的文档,并加载后面需要通过网络加载的资源。这种方式可以使资源并行加载从而使整体速度更快。需要注意的是,预解析并不改变 DOM 树,它将这个工作留给主解析过程,自己只解析外部资源的引用,比如外部脚本、样式表及图片。

li 与 li 之间有看不见的空白间隔是什么原因引起的?如何解决?

浏览器会把inline内联元素间的空白字符(空格、换行、Tab等)渲染成一个空格。为了美观,通常是一个

  • 放在一行,这导致
  • 换行后产生换行字符,它变成一个空格,占用了一个字符的宽度。

    解决办法:

    (1)为

  • 设置float:left。不足:有些容器是不能设置浮动,如左右切换的焦点图等。

    (2)将所有

  • 写在同一行。不足:代码不美观。

    (3)将

      内的字符尺寸直接设为0,即font-size:0。不足:
        中的其他字符尺寸也被设为0,需要额外重新设定其他字符尺寸,且在Safari浏览器依然会出现空白间隔。

        (4)消除

          的字符间隔letter-spacing:-8px,不足:这也设置了
        • 内的字符间隔,因此需要将
        • 内的字符间隔设为默认letter-spacing:normal。

          DNS 记录和报文

          DNS 服务器中以资源记录的形式存储信息,每一个 DNS 响应报文一般包含多条资源记录。一条资源记录的具体的格式为

          (Name,Value,Type,TTL)

          其中 TTL 是资源记录的生存时间,它定义了资源记录能够被其他的 DNS 服务器缓存多长时间。

          常用的一共有四种 Type 的值,分别是 A、NS、CNAME 和 MX ,不同 Type 的值,对应资源记录代表的意义不同:

          • 如果 Type = A,则 Name 是主机名,Value 是主机名对应的 IP 地址。因此一条记录为 A 的资源记录,提供了标 准的主机名到 IP 地址的映射。
          • 如果 Type = NS,则 Name 是个域名,Value 是负责该域名的 DNS 服务器的主机名。这个记录主要用于 DNS 链式 查询时,返回下一级需要查询的 DNS 服务器的信息。
          • 如果 Type = CNAME,则 Name 为别名,Value 为该主机的规范主机名。该条记录用于向查询的主机返回一个主机名 对应的规范主机名,从而告诉查询主机去查询这个主机名的 IP 地址。主机别名主要是为了通过给一些复杂的主机名提供 一个便于记忆的简单的别名。
          • 如果 Type = MX,则 Name 为一个邮件服务器的别名,Value 为邮件服务器的规范主机名。它的作用和 CNAME 是一 样的,都是为了解决规范主机名不利于记忆的缺点。

          扩展运算符的作用及使用场景

          (1)对象扩展运算符

          对象的扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中。

          let bar = { a: 1, b: 2 };
          let baz = { ...bar }; // { a: 1, b: 2 }

          上述方法实际上等价于:

          let bar = { a: 1, b: 2 };
          let baz = Object.assign({}, bar); // { a: 1, b: 2 }

          Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。(如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性)。

          同样,如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。

          let bar = {a: 1, b: 2};
          let baz = {...bar, ...{a:2, b: 4}};  // {a: 2, b: 4}

          利用上述特性就可以很方便的修改对象的部分属性。在redux中的reducer函数规定必须是一个纯函数reducer中的state对象要求不能直接修改,可以通过扩展运算符把修改路径的对象都复制一遍,然后产生一个新的对象返回。

          需要注意:扩展运算符对对象实例的拷贝属于浅拷贝

          (2)数组扩展运算符

          数组的扩展运算符可以将一个数组转为用逗号分隔的参数序列,且每次只能展开一层数组。

          console.log(...[1, 2, 3])
          // 1 2 3
          console.log(...[1, [2, 3, 4], 5])
          // 1 [2, 3, 4] 5

          下面是数组的扩展运算符的应用:

          • 将数组转换为参数序列
          function add(x, y) {return x + y;
          }
          const numbers = [1, 2];
          add(...numbers) // 3
          • 复制数组
          const arr1 = [1, 2];
          const arr2 = [...arr1];

          要记住:扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中,这里参数对象是个数组,数组里面的所有对象都是基础数据类型,将所有基础数据类型重新拷贝到新的数组中。

          • 合并数组

          如果想在数组内合并数组,可以这样:

          const arr1 = ['two', 'three'];const arr2 = ['one', ...arr1, 'four', 'five'];// ["one", "two", "three", "four", "five"]
          • 扩展运算符与解构赋值结合起来,用于生成数组
          const [first, ...rest] = [1, 2, 3, 4, 5];first // 1rest  // [2, 3, 4, 5]

          需要注意:如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。

          const [...rest, last] = [1, 2, 3, 4, 5];         // 报错const [first, ...rest, last] = [1, 2, 3, 4, 5];  // 报错
          • 将字符串转为真正的数组
          [...'hello']    // [ "h", "e", "l", "l", "o" ]
          • 任何 Iterator 接口的对象,都可以用扩展运算符转为真正的数组

          比较常见的应用是可以将某些数据结构转为数组:

          // arguments对象
          function foo() {const args = [...arguments];
          }

          用于替换es5中的Array.prototype.slice.call(arguments)写法。

          • 使用Math函数获取数组中特定的值
          const numbers = [9, 4, 7, 1];
          Math.min(...numbers); // 1
          Math.max(...numbers); // 9

          详细说明 Event loop

          众所周知 JS 是门非阻塞单线程语言,因为在最初 JS 就是为了和浏览器交互而诞生的。如果 JS 是门多线程的语言话,我们在多个线程中处理 DOM 就可能会发生问题(一个线程中新加节点,另一个线程中删除节点),当然可以引入读写锁解决这个问题。

          JS 在执行的过程中会产生执行环境,这些执行环境会被顺序的加入到执行栈中。如果遇到异步的代码,会被挂起并加入到 Task(有多种 task) 队列中。一旦执行栈为空,Event Loop 就会从 Task 队列中拿出需要执行的代码并放入执行栈中执行,所以本质上来说 JS 中的异步还是同步行为。

          console.log('script start');setTimeout(function() {console.log('setTimeout');
          }, 0);console.log('script end');

          以上代码虽然 setTimeout 延时为 0,其实还是异步。这是因为 HTML5 标准规定这个函数第二个参数不得小于 4 毫秒,不足会自动增加。所以 setTimeout 还是会在 script end 之后打印。

          不同的任务源会被分配到不同的 Task 队列中,任务源可以分为 微任务(microtask) 和 宏任务(macrotask)。在 ES6 规范中,microtask 称为 jobs,macrotask 称为 task

          console.log('script start');setTimeout(function() {console.log('setTimeout');
          }, 0);new Promise((resolve) => {console.log('Promise')resolve()
          }).then(function() {console.log('promise1');
          }).then(function() {console.log('promise2');
          });console.log('script end');
          // script start => Promise => script end => promise1 => promise2 => setTimeout

          以上代码虽然 setTimeout 写在 Promise 之前,但是因为 Promise 属于微任务而 setTimeout 属于宏任务,所以会有以上的打印。

          微任务包括 process.nextTickpromiseObject.observeMutationObserver

          宏任务包括 scriptsetTimeoutsetIntervalsetImmediateI/OUI rendering

          很多人有个误区,认为微任务快于宏任务,其实是错误的。因为宏任务中包括了 script ,浏览器会先执行一个宏任务,接下来有异步代码的话就先执行微任务。

          所以正确的一次 Event loop 顺序是这样的

          1. 执行同步代码,这属于宏任务
          2. 执行栈为空,查询是否有微任务需要执行
          3. 执行所有微任务
          4. 必要的话渲染 UI
          5. 然后开始下一轮 Event loop,执行宏任务中的异步代码

          通过上述的 Event loop 顺序可知,如果宏任务中的异步代码有大量的计算并且需要操作 DOM 的话,为了更快的 界面响应,我们可以把操作 DOM 放入微任务中。

          Node 中的 Event loop

          Node 中的 Event loop 和浏览器中的不相同。

          Node 的 Event loop 分为6个阶段,它们会按照顺序反复运行

          ┌───────────────────────┐
          ┌─>│        timers         │
          │  └──────────┬────────────┘
          │  ┌──────────┴────────────┐
          │  │     I/O callbacks     │
          │  └──────────┬────────────┘
          │  ┌──────────┴────────────┐
          │  │     idle, prepare     │
          │  └──────────┬────────────┘      ┌───────────────┐
          │  ┌──────────┴────────────┐      │   incoming:   │
          │  │         poll          │<──connections───     │
          │  └──────────┬────────────┘      │   data, etc.  │
          │  ┌──────────┴────────────┐      └───────────────┘
          │  │        check          │
          │  └──────────┬────────────┘
          │  ┌──────────┴────────────┐
          └──┤    close callbacks    │└───────────────────────┘
          timer

          timers 阶段会执行 setTimeoutsetInterval

          一个 timer 指定的时间并不是准确时间,而是在达到这个时间后尽快执行回调,可能会因为系统正在执行别的事务而延迟。

          下限的时间有一个范围:[1, 2147483647] ,如果设定的时间不在这个范围,将被设置为1。

          I/O

          I/O 阶段会执行除了 close 事件,定时器和 setImmediate 的回调

          idle, prepare

          idle, prepare 阶段内部实现

          poll

          poll 阶段很重要,这一阶段中,系统会做两件事情

          1. 执行到点的定时器
          2. 执行 poll 队列中的事件

          并且当 poll 中没有定时器的情况下,会发现以下两件事情

          • 如果 poll 队列不为空,会遍历回调队列并同步执行,直到队列为空或者系统限制
          • 如果 poll 队列为空,会有两件事发生
            • 如果有 setImmediate 需要执行,poll 阶段会停止并且进入到 check 阶段执行 setImmediate
            • 如果没有 setImmediate 需要执行,会等待回调被加入到队列中并立即执行回调

          如果有别的定时器需要被执行,会回到 timer 阶段执行回调。

          check

          check 阶段执行 setImmediate

          close callbacks

          close callbacks 阶段执行 close 事件

          并且在 Node 中,有些情况下的定时器执行顺序是随机的

          setTimeout(() => {console.log('setTimeout');
          }, 0);
          setImmediate(() => {console.log('setImmediate');
          })
          // 这里可能会输出 setTimeout,setImmediate
          // 可能也会相反的输出,这取决于性能
          // 因为可能进入 event loop 用了不到 1 毫秒,这时候会执行 setImmediate
          // 否则会执行 setTimeout

          当然在这种情况下,执行顺序是相同的

          var fs = require('fs')fs.readFile(__filename, () => {setTimeout(() => {console.log('timeout');}, 0);setImmediate(() => {console.log('immediate');});
          });
          // 因为 readFile 的回调在 poll 中执行
          // 发现有 setImmediate ,所以会立即跳到 check 阶段执行回调
          // 再去 timer 阶段执行 setTimeout
          // 所以以上输出一定是 setImmediate,setTimeout

          上面介绍的都是 macrotask 的执行情况,microtask 会在以上每个阶段完成后立即执行。

          setTimeout(()=>{console.log('timer1')Promise.resolve().then(function() {console.log('promise1')})
          }, 0)setTimeout(()=>{console.log('timer2')Promise.resolve().then(function() {console.log('promise2')})
          }, 0)// 以上代码在浏览器和 node 中打印情况是不同的
          // 浏览器中打印 timer1, promise1, timer2, promise2
          // node 中打印 timer1, timer2, promise1, promise2

          Node 中的 process.nextTick 会先于其他 microtask 执行。

          setTimeout(() => {console.log("timer1");Promise.resolve().then(function() {console.log("promise1");});
          }, 0);process.nextTick(() => {console.log("nextTick");
          });
          // nextTick, timer1, promise1
          

          说一下类组件和函数组件的区别?

          1. 语法上的区别:函数式组件是一个纯函数,它是需要接受props参数并且返回一个React元素就可以了。类组件是需要继承React.Component的,而且class组件需要创建render并且返回React元素,语法上来讲更复杂。2. 调用方式函数式组件可以直接调用,返回一个新的React元素;类组件在调用时是需要创建一个实例的,然后通过调用实例里的render方法来返回一个React元素。3. 状态管理函数式组件没有状态管理,类组件有状态管理。4. 使用场景类组件没有具体的要求。函数式组件一般是用在大型项目中来分割大组件(函数式组件不用创建实例,所有更高效),一般情况下能用函数式组件就不用类组件,提升效率。

          浏览器乱码的原因是什么?如何解决?

          产生乱码的原因:

          • 网页源代码是gbk的编码,而内容中的中文字是utf-8编码的,这样浏览器打开即会出现html乱码,反之也会出现乱码;
          • html网页编码是gbk,而程序从数据库中调出呈现是utf-8编码的内容也会造成编码乱码;
          • 浏览器不能自动检测网页编码,造成网页乱码。

          解决办法:

          • 使用软件编辑HTML网页内容;
          • 如果网页设置编码是gbk,而数据库储存数据编码格式是UTF-8,此时需要程序查询数据库数据显示数据前进程序转码;
          • 如果浏览器浏览时候出现网页乱码,在浏览器中找到转换编码的菜单进行转换。
  • 相关内容

    热门资讯

    埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
    世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
    北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
    苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
    应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
    脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
    长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
    demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
    猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
    埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
    苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
    长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
    北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...