关于javascript:大厂前端面试考什么

1次阅读

共计 22479 个字符,预计需要花费 57 分钟才能阅读完成。

async/await 如何捕捉异样

async function fn(){
    try{let a = await Promise.reject('error')
    }catch(error){console.log(error)
    }
}

Promise.reject

Promise.reject = function(reason) {return new Promise((resolve, reject) => reject(reason));
}

如何进攻 XSS 攻打?

能够看到 XSS 危害如此之大,那么在开发网站时就要做好进攻措施,具体措施如下:

  • 能够从浏览器的执行来进行预防,一种是应用纯前端的形式,不必服务器端拼接后返回(不应用服务端渲染)。另一种是对须要插入到 HTML 中的代码做好充沛的本义。对于 DOM 型的攻打,次要是前端脚本的不牢靠而造成的,对于数据获取渲染和字符串拼接的时候应该对可能呈现的恶意代码状况进行判断。
  • 应用 CSP,CSP 的实质是建设一个白名单,通知浏览器哪些内部资源能够加载和执行,从而避免恶意代码的注入攻打。
  1. CSP 指的是内容安全策略,它的实质是建设一个白名单,通知浏览器哪些内部资源能够加载和执行。咱们只须要配置规定,如何拦挡由浏览器本人来实现。
  2. 通常有两种形式来开启 CSP,一种是设置 HTTP 首部中的 Content-Security-Policy,一种是设置 meta 标签的形式
  • 对一些敏感信息进行爱护,比方 cookie 应用 http-only,使得脚本无奈获取。也能够应用验证码,防止脚本伪装成用户执行一些操作。

浏览器渲染过程的线程有哪些

浏览器的渲染过程的线程总共有五种:(1)GUI 渲染线程 负责渲染浏览器页面,解析 HTML、CSS,构建 DOM 树、构建 CSSOM 树、构建渲染树和绘制页面;当界面须要 重绘 或因为某种操作引发 回流 时,该线程就会执行。

留神:GUI 渲染线程和 JS 引擎线程是互斥的,当 JS 引擎执行时 GUI 线程会被挂起,GUI 更新会被保留在一个队列中等到 JS 引擎闲暇时立刻被执行。

(2)JS 引擎线程 JS 引擎线程也称为 JS 内核,负责解决 Javascript 脚本程序,解析 Javascript 脚本,运行代码;JS 引擎线程始终期待着工作队列中工作的到来,而后加以解决,一个 Tab 页中无论什么时候都只有一个 JS 引擎线程在运行 JS 程序;

留神:GUI 渲染线程与 JS 引擎线程的互斥关系,所以如果 JS 执行的工夫过长,会造成页面的渲染不连贯,导致页面渲染加载阻塞。

(3)工夫触发线程 工夫触发线程 属于浏览器而不是 JS 引擎,用来管制事件循环;当 JS 引擎执行代码块如 setTimeOut 时(也可是来自浏览器内核的其余线程, 如鼠标点击、AJAX 异步申请等),会将对应工作增加到事件触发线程中;当对应的事件合乎触发条件被触发时,该线程会把事件增加到待处理队列的队尾,期待 JS 引擎的解决;

留神:因为 JS 的单线程关系,所以这些待处理队列中的事件都得排队期待 JS 引擎解决(当 JS 引擎闲暇时才会去执行);

(4)定时器触发过程 定时器触发过程 即 setInterval 与 setTimeout 所在线程;浏览器定时计数器并不是由 JS 引擎计数的,因为 JS 引擎是单线程的,如果处于阻塞线程状态就会影响记计时的准确性;因而应用独自线程来计时并触发定时器,计时结束后,增加到事件队列中,期待 JS 引擎闲暇后执行,所以定时器中的工作在设定的工夫点不肯定可能准时执行,定时器只是在指定工夫点将工作增加到事件队列中;

留神:W3C 在 HTML 规范中规定,定时器的定时工夫不能小于 4ms,如果是小于 4ms,则默认为 4ms。

(5)异步 http 申请线程

  • XMLHttpRequest 连贯后通过浏览器新开一个线程申请;
  • 检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将回调函数放入事件队列中,期待 JS 引擎闲暇后执行;

Vue 通信

1.props 和 $emit
2. 地方事件总线 EventBus(根本不必)
3.vuex(官网举荐状态管理器)
4.$parent 和 $children
当然还有一些其余方法,但根本不罕用,或者用起来太简单来。介绍来通信的形式,还能够扩大说一下应用
场景,如何应用,注意事项之类的。

什么是原型什么是原型链?

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>

</body>
<script>
    function Person () {}    var person  = new Person();    person.name = 'Kevin';    console.log(person.name) // Kevin

    // prototype
    function Person () {}    Person.prototype.name = 'Kevin';    var person1 = new Person();    var person2 = new Person();    console.log(person1.name)// Kevin
    console.log(person2.name)// Kevin

    // __proto__
    function Person () {}    var person = new Person();    console.log(person.__proto__ === Person.prototype) // true

    //constructor
    function Person() {}    console.log(Person === Person.prototype.constructor) // true

    // 综上所述
    function Person () {}    var person = new Person()    console.log(person.__proto__ == Person.prototype) // true
    console.log(Person.prototype.constructor == Person) // true
    // 顺便学习一下 ES5 得办法, 能够取得对象得原型
    console.log(Object.getPrototypeOf(person) === Person.prototype) // true

    // 实例与原型
    function Person () {}    Person.prototype.name = 'Kevin';    var person = new Person();    person.name = 'Daisy';    console.log(person.name) // Daisy
    delete person.name;    console.log(person.name) // Kevin

    // 原型得原型
    var obj = new Object();    obj.name = 'Kevin',    console.log(obj.name) //Kevin

     // 原型链
     console.log(Object.prototype.__proto__ === null) //true
     // null 示意 "没用对象" 即该处不应该有值

     // 补充
     function Person() {}     var person = new Person()     console.log(person.constructor === Person) // true
     // 当获取 person.constructor 时,其实 person 中并没有 constructor 属性, 当不能读取到 constructor 属性时, 会从 person 的原型
     // 也就是 Person.prototype 中读取时, 正好原型中有该属性, 所以
     person.constructor === Person.prototype.constructor

     //__proto__
     // 其次是__proto__,绝大部分浏览器都反对这个非标准的办法拜访原型,然而它并不存在于 Person.prototype 中,实际上,它
     // 是来自与 Object.prototype, 与其说是一个属性,不如说是一个 getter/setter, 当应用 obj.__proto__时,能够了解成返回了
     // Object.getPrototypeOf(obj)
     总结:1、当一个对象查找属性和办法时会从本身查找, 如果查找不到则会通过__proto__指向被实例化的构造函数的 prototype     2、隐式原型也是一个对象, 是指向咱们构造函数的原型     3、除了最顶层的 Object 对象没有__proto_,其余所有的对象都有__proto__, 这是隐式原型     4、隐式原型__proto__的作用是让对象通过它来始终往上查找属性或办法,直到找到最顶层的 Object 的__proto__属性,它的值是 null, 这个查找的过程就是原型链



</script>
</html>

首屏和白屏工夫如何计算

首屏工夫的计算,能够由 Native WebView 提供的相似 onload 的办法实现,在 ios 下对应的是 webViewDidFinishLoad,在 android 下对应的是 onPageFinished 事件。

白屏的定义有多种。能够认为“没有任何内容”是白屏,能够认为“网络或服务异样”是白屏,能够认为“数据加载中”是白屏,能够认为“图片加载不进去”是白屏。场景不同,白屏的计算形式就不雷同。

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

如何提⾼ webpack 的打包速度?

(1)优化 Loader

对于 Loader 来说,影响打包效率首当其冲必属 Babel 了。因为 Babel 会将代码转为字符串生成 AST,而后对 AST 持续进行转变最初再生成新的代码,我的项目越大,转换代码越多,效率就越低。当然了,这是能够优化的。

首先咱们 优化 Loader 的文件搜寻范畴

module.exports = {
  module: {
    rules: [
      {
        // js 文件才应用 babel
        test: /\.js$/,
        loader: 'babel-loader',
        // 只在 src 文件夹下查找
        include: [resolve('src')],
        // 不会去查找的门路
        exclude: /node_modules/
      }
    ]
  }
}

对于 Babel 来说,心愿只作用在 JS 代码上的,而后 node_modules 中应用的代码都是编译过的,所以齐全没有必要再去解决一遍。

当然这样做还不够,还能够将 Babel 编译过的文件 缓存 起来,下次只须要编译更改过的代码文件即可,这样能够大幅度放慢打包工夫

loader: 'babel-loader?cacheDirectory=true'

(2)HappyPack

受限于 Node 是单线程运行的,所以 Webpack 在打包的过程中也是单线程的,特地是在执行 Loader 的时候,长时间编译的工作很多,这样就会导致期待的状况。

HappyPack 能够将 Loader 的同步执行转换为并行的,这样就能充分利用系统资源来放慢打包效率了

module: {
  loaders: [
    {
      test: /\.js$/,
      include: [resolve('src')],
      exclude: /node_modules/,
      // id 前面的内容对应上面
      loader: 'happypack/loader?id=happybabel'
    }
  ]
},
plugins: [
  new HappyPack({
    id: 'happybabel',
    loaders: ['babel-loader?cacheDirectory'],
    // 开启 4 个线程
    threads: 4
  })
]

(3)DllPlugin

DllPlugin 能够将特定的类库提前打包而后引入。这种形式能够极大的缩小打包类库的次数,只有当类库更新版本才有须要从新打包,并且也实现了将公共代码抽离成独自文件的优化计划。DllPlugin 的应用办法如下:

// 独自配置在一个文件中
// webpack.dll.conf.js
const path = require('path')
const webpack = require('webpack')
module.exports = {
  entry: {
    // 想对立打包的类库
    vendor: ['react']
  },
  output: {path: path.join(__dirname, 'dist'),
    filename: '[name].dll.js',
    library: '[name]-[hash]'
  },
  plugins: [
    new webpack.DllPlugin({
      // name 必须和 output.library 统一
      name: '[name]-[hash]',
      // 该属性须要与 DllReferencePlugin 中统一
      context: __dirname,
      path: path.join(__dirname, 'dist', '[name]-manifest.json')
    })
  ]
}

而后须要执行这个配置文件生成依赖文件,接下来须要应用 DllReferencePlugin 将依赖文件引入我的项目中

// webpack.conf.js
module.exports = {
  // ... 省略其余配置
  plugins: [
    new webpack.DllReferencePlugin({
      context: __dirname,
      // manifest 就是之前打包进去的 json 文件
      manifest: require('./dist/vendor-manifest.json'),
    })
  ]
}

(4)代码压缩

在 Webpack3 中,个别应用 UglifyJS 来压缩代码,然而这个是单线程运行的,为了放慢效率,能够应用 webpack-parallel-uglify-plugin 来并行运行 UglifyJS,从而提高效率。

在 Webpack4 中,不须要以上这些操作了,只须要将 mode 设置为 production 就能够默认开启以上性能。代码压缩也是咱们必做的性能优化计划,当然咱们不止能够压缩 JS 代码,还能够压缩 HTML、CSS 代码,并且在压缩 JS 代码的过程中,咱们还能够通过配置实现比方删除 console.log 这类代码的性能。

(5)其余

能够通过一些小的优化点来放慢打包速度

  • resolve.extensions:用来表明文件后缀列表,默认查找程序是 ['.js', '.json'],如果你的导入文件没有增加后缀就会依照这个程序查找文件。咱们应该尽可能减少后缀列表长度,而后将呈现频率高的后缀排在后面
  • resolve.alias:能够通过别名的形式来映射一个门路,能让 Webpack 更快找到门路
  • module.noParse:如果你确定一个文件下没有其余依赖,就能够应用该属性让 Webpack 不扫描该文件,这种形式对于大型的类库很有帮忙

setTimeout 模仿 setInterval

形容 :应用setTimeout 模仿实现 setInterval 的性能。

实现

const mySetInterval(fn, time) {
    let timer = null;
    const interval = () => {timer = setTimeout(() => {fn();  // time 工夫之后会执行真正的函数 fn
            interval();  // 同时再次调用 interval 自身}, time)
    }
    interval();  // 开始执行
    // 返回用于敞开定时器的函数
    return () => clearTimeout(timer);
}

// 测试
const cancel = mySetInterval(() => console.log(1), 400);
setTimeout(() => {cancel();
}, 1000);  
// 打印两次 1

Node 中的 Event Loop 和浏览器中的有什么区别?process.nextTick 执行程序?

Node 中的 Event Loop 和浏览器中的是齐全不雷同的货色。

Node 的 Event Loop 分为 6 个阶段,它们会依照 程序 重复运行。每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量达到零碎设定的阈值,就会进入下一阶段。

(1)Timers(计时器阶段):首次进入事件循环,会从计时器阶段开始。此阶段会判断是否存在过期的计时器回调(蕴含 setTimeout 和 setInterval),如果存在则会执行所有过期的计时器回调,执行结束后,如果回调中触发了相应的微工作,会接着执行所有微工作,执行完微工作后再进入 Pending callbacks 阶段。

(2)Pending callbacks:执行推延到下一个循环迭代的 I / O 回调(零碎调用相干的回调)。

(3)Idle/Prepare:仅供外部应用。

(4)Poll(轮询阶段)

  • 当回调队列不为空时:会执行回调,若回调中触发了相应的微工作,这里的微工作执行机会和其余中央有所不同,不会等到所有回调执行结束后才执行,而是针对每一个回调执行结束后,就执行相应微工作。执行完所有的回调后,变为上面的状况。
  • 当回调队列为空时(没有回调或所有回调执行结束):但如果存在有计时器(setTimeout、setInterval 和 setImmediate)没有执行,会完结轮询阶段,进入 Check 阶段。否则会阻塞并期待任何正在执行的 I / O 操作实现,并马上执行相应的回调,直到所有回调执行结束。

(5)Check(查问阶段):会查看是否存在 setImmediate 相干的回调,如果存在则执行所有回调,执行结束后,如果回调中触发了相应的微工作,会接着执行所有微工作,执行完微工作后再进入 Close callbacks 阶段。

(6)Close callbacks:执行一些敞开回调,比方 socket.on(‘close’, …)等。

上面来看一个例子,首先在有些状况下,定时器的执行程序其实是 随机

setTimeout(() => {    console.log('setTimeout')}, 0)setImmediate(() => {    console.log('setImmediate')})

对于以上代码来说,setTimeout 可能执行在前,也可能执行在后

  • 首先 setTimeout(fn, 0) === setTimeout(fn, 1),这是由源码决定的
  • 进入事件循环也是须要老本的,如果在筹备时候破费了大于 1ms 的工夫,那么在 timer 阶段就会间接执行 setTimeout 回调
  • 那么如果筹备工夫破费小于 1ms,那么就是 setImmediate 回调先执行了

当然在某些状况下,他们的执行程序肯定是固定的,比方以下代码:

const fs = require('fs')
fs.readFile(__filename, () => {setTimeout(() => {console.log('timeout');
    }, 0)
    setImmediate(() => {console.log('immediate')
    })
})

在上述代码中,setImmediate 永远 先执行。因为两个代码写在 IO 回调中,IO 回调是在 poll 阶段执行,当回调执行结束后队列为空,发现存在 setImmediate 回调,所以就间接跳转到 check 阶段去执行回调了。

下面都是 macrotask 的执行状况,对于 microtask 来说,它会在以上每个阶段实现前 清空 microtask 队列,

setTimeout(() => {console.log('timer21')
}, 0)
Promise.resolve().then(function() {console.log('promise1')
})

对于以上代码来说,其实和浏览器中的输入是一样的,microtask 永远执行在 macrotask 后面。

最初来看 Node 中的 process.nextTick,这个函数其实是独立于 Event Loop 之外的,它有一个本人的队列,当每个阶段实现后,如果存在 nextTick 队列,就会 清空队列中的所有回调函数,并且优先于其余 microtask 执行。

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

对于以上代码,永远都是先把 nextTick 全副打印进去。

什么是中间人攻打?如何防备中间人攻打?

两头⼈ (Man-in-the-middle attack, MITM) 是指攻击者与通信的两端别离创立独⽴的分割, 并替换其所收到的数据, 使通信的两端认为他们正在通过⼀个私密的连贯与对⽅直接对话, 但事实上整个会话都被攻击者齐全管制。在两头⼈攻打中,攻击者能够拦挡通信双⽅的通话并插⼊新的内容。

攻打过程如下:

  • 客户端发送申请到服务端,申请被两头⼈截获
  • 服务器向客户端发送公钥
  • 两头⼈截获公钥,保留在⾃⼰⼿上。而后⾃⼰⽣成⼀个 伪造的 公钥,发给客户端
  • 客户端收到伪造的公钥后,⽣成加密 hash 值发给服务器
  • 两头⼈取得加密 hash 值,⽤⾃⼰的私钥解密取得真秘钥, 同时⽣成假的加密 hash 值,发给服务器
  • 服务器⽤私钥解密取得假密钥, 而后加密数据传输给客户端

Promise.all

形容:所有 promise 的状态都变成 fulfilled,就会返回一个状态为 fulfilled 的数组(所有promisevalue)。只有有一个失败,就返回第一个状态为 rejectedpromise 实例的 reason

实现

Promise.all = function(promises) {return new Promise((resolve, reject) => {if(Array.isArray(promises)) {if(promises.length === 0) return resolve(promises);
            let result = [];
            let count = 0;
            promises.forEach((item, index) => {Promise.resolve(item).then(
                    value => {
                        count++;
                        result[index] = value;
                        if(count === promises.length) resolve(result);
                    }, 
                    reason => reject(reason)
                );
            })
        }
        else return reject(new TypeError("Argument is not iterable"));
    });
}

正向代理和反向代理的区别

  • 正向代理:

客户端想取得一个服务器的数据,然而因为种种原因无奈间接获取。于是客户端设置了一个代理服务器,并且指定指标服务器,之后代理服务器向指标服务器转交申请并将取得的内容发送给客户端。这样实质上起到了对实在服务器暗藏实在客户端的目标。实现正向代理须要批改客户端,比方批改浏览器配置。

  • 反向代理:

服务器为了可能将工作负载分不到多个服务器来进步网站性能 (负载平衡)等目标,当其受到申请后,会首先依据转发规定来确定申请应该被转发到哪个服务器上,而后将申请转发到对应的实在服务器上。这样实质上起到了对客户端暗藏实在服务器的作用。
个别应用反向代理后,须要通过批改 DNS 让域名解析到代理服务器 IP,这时浏览器无奈察觉到真正服务器的存在,当然也就不须要批改配置了。

正向代理和反向代理的构造是一样的,都是 client-proxy-server 的构造,它们次要的区别就在于两头这个 proxy 是哪一方设置的。在正向代理中,proxy 是 client 设置的,用来暗藏 client;而在反向代理中,proxy 是 server 设置的,用来暗藏 server。

说一下 web worker

在 HTML 页面中,如果在执行脚本时,页面的状态是不可相应的,直到脚本执行实现后,页面才变成可相应。web worker 是运行在后盾的 js,独立于其余脚本,不会影响页面的性能。并且通过 postMessage 将后果回传到主线程。这样在进行简单操作的时候,就不会阻塞主线程了。

如何创立 web worker:

  1. 检测浏览器对于 web worker 的支持性
  2. 创立 web worker 文件(js,回传函数等)
  3. 创立 web worker 对象

懒加载的概念

懒加载也叫做提早加载、按需加载,指的是在长网页中提早加载图片数据,是一种较好的网页性能优化的形式。在比拟长的网页或利用中,如果图片很多,所有的图片都被加载进去,而用户只能看到可视窗口的那一部分图片数据,这样就节约了性能。

如果应用图片的懒加载就能够解决以上问题。在滚动屏幕之前,可视化区域之外的图片不会进行加载,在滚动屏幕时才加载。这样使得网页的加载速度更快,缩小了服务器的负载。懒加载实用于图片较多,页面列表较长(长列表)的场景中。

说一说 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: 也是在所有同源窗口中都是共享的. 也就是说只有浏览器不敞开,数据依然存在

说一说你用过的 css 布局

gird 布局,layout 布局,flex 布局,双飞翼,圣杯布局等

如何解决逾越问题

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

闭包

首先阐明什么是闭包,闭包简略来说就是函数嵌套函数,外部函数援用来内部函数的变量,从而导致垃圾回收
机制没有把以后变量回收掉,这样的操作带来了内存透露的影响,当内存透露到肯定水平会影响你的我的项目运行
变得卡顿等等问题。因而在我的项目中咱们要尽量避免内存透露。

图片懒加载

实现getBoundClientRect 的实现形式,监听 scroll 事件(倡议给监听事件增加节流),图片加载完会从 img 标签组成的 DOM 列表中删除,最初所有的图片加载结束后须要解绑监听事件。

// scr 加载默认图片,data-src 保留施行懒加载后的图片
// <img src="./default.jpg" data-src="https://xxx.jpg" alt="" />
let imgs = [...document.querySelectorAll("img")];
const len = imgs.length;

let lazyLoad = function() {
    let count = 0;
    let deleteImgs = [];
    // 获取以后可视区的高度
    let viewHeight = document.documentElement.clientHeight;
    // 获取以后滚动条的地位(间隔顶部的间隔, 等价于 document.documentElement.scrollTop)
    let scrollTop = window.pageYOffset;
    imgs.forEach((img) => {
        // 获取元素的大小,及其绝对于视口的地位,如 bottom 为元素底部到网页顶部的间隔
        let bound = img.getBoundingClientRect();
        // 以后图片间隔网页顶部的间隔
        // let imgOffsetTop = img.offsetTop;

        // 判断图片是否在可视区内,如果在就加载(两种判断形式)// if(imgOffsetTop < scrollTop + viewHeight) 
        if (bound.top < viewHeight) {
            img.src = img.dataset.src;  // 替换待加载的图片 src
            count++;
            deleteImgs.push(img);
            // 最初所有的图片加载结束后须要解绑监听事件
            if(count === len) {document.removeEventListener("scroll", imgThrottle);
            }
        }
    });
    // 图片加载完会从 `img` 标签组成的 DOM 列表中删除
    imgs = imgs.filter((img) => !deleteImgs.includes(img));
}

window.onload = function () {lazyLoad();
};
// 应用 防抖 / 节流 优化一下滚动事件
let imgThrottle = debounce(lazyLoad, 1000);
// 监听 `scroll` 事件
window.addEventListener("scroll", imgThrottle);
正文完
 0