关于javascript:前端面试中小型公司都考些什么

54次阅读

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

什么是 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 都属于服务端的安全漏洞。

Nginx 的概念及其工作原理

Nginx 是一款轻量级的 Web 服务器,也能够用于反向代理、负载平衡和 HTTP 缓存等。Nginx 应用异步事件驱动的办法来解决申请,是一款面向性能设计的 HTTP 服务器。

传统的 Web 服务器如 Apache 是 process-based 模型的,而 Nginx 是基于 event-driven 模型的。正是这个次要的区别带给了 Nginx 在性能上的劣势。

Nginx 架构的最顶层是一个 master process,这个 master process 用于产生其余的 worker process,这一点和 Apache 十分像,然而 Nginx 的 worker process 能够同时解决大量的 HTTP 申请,而每个 Apache process 只能解决一个。

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

  • 正向代理:

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

  • 反向代理:

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

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

documentFragment 是什么?用它跟间接操作 DOM 的区别是什么?

MDN 中对 documentFragment 的解释:

DocumentFragment,文档片段接口,一个没有父对象的最小文档对象。它被作为一个轻量版的 Document 应用,就像规范的 document 一样,存储由节点(nodes)组成的文档构造。与 document 相比,最大的区别是 DocumentFragment 不是实在 DOM 树的一部分,它的变动不会触发 DOM 树的从新渲染,且不会导致性能等问题。

当咱们把一个 DocumentFragment 节点插入文档树时,插入的不是 DocumentFragment 本身,而是它的所有子孙节点。在频繁的 DOM 操作时,咱们就能够将 DOM 元素插入 DocumentFragment,之后一次性的将所有的子孙节点插入文档中。和间接操作 DOM 相比,将 DocumentFragment 节点插入 DOM 树时,不会触发页面的重绘,这样就大大提高了页面的性能。

代码输入后果

const promise = new Promise((resolve, reject) => {console.log(1);
  setTimeout(() => {console.log("timerStart");
    resolve("success");
    console.log("timerEnd");
  }, 0);
  console.log(2);
});
promise.then((res) => {console.log(res);
});
console.log(4);

输入后果如下:

1
2
4
timerStart
timerEnd
success

代码执行过程如下:

  • 首先遇到 Promise 构造函数,会先执行外面的内容,打印1
  • 遇到定时器steTimeout,它是一个宏工作,放入宏工作队列;
  • 持续向下执行,打印出 2;
  • 因为 Promise 的状态此时还是 pending,所以promise.then 先不执行;
  • 继续执行上面的同步工作,打印出 4;
  • 此时微工作队列没有工作,继续执行下一轮宏工作,执行steTimeout
  • 首先执行 timerStart,而后遇到了resolve,将promise 的状态改为 resolved 且保留后果并将之前的 promise.then 推入微工作队列,再执行timerEnd
  • 执行完这个宏工作,就去执行微工作 promise.then,打印出resolve 的后果。

懒加载与预加载的区别

这两种形式都是进步网页性能的形式,两者次要区别是一个是提前加载,一个是缓慢甚至不加载。懒加载对服务器前端有肯定的缓解压力作用,预加载则会减少服务器前端压力。

  • 懒加载也叫提早加载,指的是在长网页中提早加载图片的机会,当用户须要拜访时,再去加载,这样能够进步网站的首屏加载速度,晋升用户的体验,并且能够缩小服务器的压力。它实用于图片很多,页面很长的电商网站的场景。懒加载的实现原理是,将页面上的图片的 src 属性设置为空字符串,将图片的实在门路保留在一个自定义属性中,当页面滚动的时候,进行判断,如果图片进入页面可视区域内,则从自定义属性中取出实在门路赋值给图片的 src 属性,以此来实现图片的提早加载。
  • 预加载指的是将所需的资源提前申请加载到本地,这样前面在须要用到时就间接从缓存取资源。 通过预加载可能缩小用户的等待时间,进步用户的体验。我理解的预加载的最罕用的形式是应用 js 中的 image 对象,通过为 image 对象来设置 scr 属性,来实现图片的预加载。

什么是 CSRF 攻打?

(1)概念

CSRF 攻打指的是 跨站申请伪造攻打,攻击者诱导用户进入一个第三方网站,而后该网站向被攻打网站发送跨站申请。如果用户在被攻打网站中保留了登录状态,那么攻击者就能够利用这个登录状态,绕过后盾的用户验证,假冒用户向服务器执行一些操作。

CSRF 攻打的 实质是利用 cookie 会在同源申请中携带发送给服务器的特点,以此来实现用户的假冒。

(2)攻打类型

常见的 CSRF 攻打有三种:

  • GET 类型的 CSRF 攻打,比方在网站中的一个 img 标签里构建一个申请,当用户关上这个网站的时候就会主动发动提交。
  • POST 类型的 CSRF 攻打,比方构建一个表单,而后暗藏它,当用户进入页面时,主动提交这个表单。
  • 链接类型的 CSRF 攻打,比方在 a 标签的 href 属性里构建一个申请,而后诱导用户去点击。

参考:前端进阶面试题具体解答

setTimeout(fn, 0)多久才执行,Event Loop

setTimeout 依照程序放到队列外面,而后期待函数调用栈清空之后才开始执行,而这些操作进入队列的程序,则由设定的延迟时间来决定

代码输入后果

console.log(1);

setTimeout(() => {console.log(2);
  Promise.resolve().then(() => {console.log(3)
  });
});

new Promise((resolve, reject) => {console.log(4)
  resolve(5)
}).then((data) => {console.log(data);
})

setTimeout(() => {console.log(6);
})

console.log(7);

代码输入后果如下:

1
4
7
5
2
3
6

代码执行过程如下:

  1. 首先执行 scrip 代码,打印出 1;
  2. 遇到第一个定时器 setTimeout,将其退出到宏工作队列;
  3. 遇到 Promise,执行外面的同步代码,打印出 4,遇到 resolve,将其退出到微工作队列;
  4. 遇到第二个定时器 setTimeout,将其退出到红工作队列;
  5. 执行 script 代码,打印出 7,至此第一轮执行实现;
  6. 指定微工作队列中的代码,打印出 resolve 的后果:5;
  7. 执行宏工作中的第一个定时器 setTimeout,首先打印出 2,而后遇到 Promise.resolve().then(),将其退出到微工作队列;
  8. 执行完这个宏工作,就开始执行微工作队列,打印出 3;
  9. 继续执行宏工作队列中的第二个定时器,打印出 6。

IndexedDB 有哪些特点?

IndexedDB 具备以下特点:

  • 键值对贮存:IndexedDB 外部采纳对象仓库(object store)存放数据。所有类型的数据都能够间接存入,包含 JavaScript 对象。对象仓库中,数据以 ” 键值对 ” 的模式保留,每一个数据记录都有对应的主键,主键是举世无双的,不能有反复,否则会抛出一个谬误。
  • 异步:IndexedDB 操作时不会锁死浏览器,用户仍然能够进行其余操作,这与 LocalStorage 造成比照,后者的操作是同步的。异步设计是为了避免大量数据的读写,拖慢网页的体现。
  • 反对事务:IndexedDB 反对事务(transaction),这意味着一系列操作步骤之中,只有有一步失败,整个事务就都勾销,数据库回滚到事务产生之前的状态,不存在只改写一部分数据的状况。
  • 同源限度: IndexedDB 受到同源限度,每一个数据库对应创立它的域名。网页只能拜访本身域名下的数据库,而不能拜访跨域的数据库。
  • 贮存空间大:IndexedDB 的贮存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有下限。
  • 反对二进制贮存:IndexedDB 不仅能够贮存字符串,还能够贮存二进制数据(ArrayBuffer 对象和 Blob 对象)。

渲染过程中遇到 JS 文件如何解决?

JavaScript 的加载、解析与执行会阻塞文档的解析,也就是说,在构建 DOM 时,HTML 解析器若遇到了 JavaScript,那么它会暂停文档的解析,将控制权移交给 JavaScript 引擎,等 JavaScript 引擎运行结束,浏览器再从中断的中央复原持续解析文档。也就是说,如果想要首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都倡议将 script 标签放在 body 标签底部的起因。当然在当下,并不是说 script 标签必须放在底部,因为你能够给 script 标签增加 defer 或者 async 属性。

代码输入后果

var length = 10;
function fn() {console.log(this.length);
}

var obj = {
  length: 5,
  method: function(fn) {fn();
    arguments[0]();}
};

obj.method(fn, 1);

输入后果:10 2

解析:

  1. 第一次执行 fn(),this 指向 window 对象,输入 10。
  2. 第二次执行 arguments[0],相当于 arguments 调用办法,this 指向 arguments,而这里传了两个参数,故输入 arguments 长度为 2。

如何提⾼ 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 不扫描该文件,这种形式对于大型的类库很有帮忙

浏览器本地存储形式及应用场景

(1)Cookie

Cookie 是最早被提出来的本地存储形式,在此之前,服务端是无奈判断网络中的两个申请是否是同一用户发动的,为解决这个问题,Cookie 就呈现了。Cookie 的大小只有 4kb,它是一种纯文本文件,每次发动 HTTP 申请都会携带 Cookie。

Cookie 的个性:

  • Cookie 一旦创立胜利,名称就无奈批改
  • Cookie 是无奈跨域名的,也就是说 a 域名和 b 域名下的 cookie 是无奈共享的,这也是由 Cookie 的隐衷安全性决定的,这样就可能阻止非法获取其余网站的 Cookie
  • 每个域名下 Cookie 的数量不能超过 20 个,每个 Cookie 的大小不能超过 4kb
  • 有平安问题,如果 Cookie 被拦挡了,那就可取得 session 的所有信息,即便加密也于事无补,无需晓得 cookie 的意义,只有转发 cookie 就能达到目标
  • Cookie 在申请一个新的页面的时候都会被发送过来

如果须要域名之间跨域共享 Cookie,有两种办法:

  1. 应用 Nginx 反向代理
  2. 在一个站点登陆之后,往其余网站写 Cookie。服务端的 Session 存储到一个节点,Cookie 存储 sessionId

Cookie 的应用场景:

  • 最常见的应用场景就是 Cookie 和 session 联合应用,咱们将 sessionId 存储到 Cookie 中,每次发申请都会携带这个 sessionId,这样服务端就晓得是谁发动的申请,从而响应相应的信息。
  • 能够用来统计页面的点击次数

(2)LocalStorage

LocalStorage 是 HTML5 新引入的个性,因为有的时候咱们存储的信息较大,Cookie 就不能满足咱们的需要,这时候 LocalStorage 就派上用场了。

LocalStorage 的长处:

  • 在大小方面,LocalStorage 的大小个别为 5MB,能够贮存更多的信息
  • LocalStorage 是长久贮存,并不会随着页面的敞开而隐没,除非被动清理,不然会永恒存在
  • 仅贮存在本地,不像 Cookie 那样每次 HTTP 申请都会被携带

LocalStorage 的毛病:

  • 存在浏览器兼容问题,IE8 以下版本的浏览器不反对
  • 如果浏览器设置为隐衷模式,那咱们将无奈读取到 LocalStorage
  • LocalStorage 受到同源策略的限度,即端口、协定、主机地址有任何一个不雷同,都不会拜访

LocalStorage 的罕用 API:

// 保留数据到 localStorage
localStorage.setItem('key', 'value');

// 从 localStorage 获取数据
let data = localStorage.getItem('key');

// 从 localStorage 删除保留的数据
localStorage.removeItem('key');

// 从 localStorage 删除所有保留的数据
localStorage.clear();

// 获取某个索引的 Key
localStorage.key(index)

LocalStorage 的应用场景:

  • 有些网站有换肤的性能,这时候就能够将换肤的信息存储在本地的 LocalStorage 中,当须要换肤的时候,间接操作 LocalStorage 即可
  • 在网站中的用户浏览信息也会存储在 LocalStorage 中,还有网站的一些不常变动的个人信息等也能够存储在本地的 LocalStorage 中

(3)SessionStorage

SessionStorage 和 LocalStorage 都是在 HTML5 才提出来的存储计划,SessionStorage 次要用于长期保留同一窗口 (或标签页) 的数据,刷新页面时不会删除,敞开窗口或标签页之后将会删除这些数据。

SessionStorage 与 LocalStorage 比照:

  • SessionStorage 和 LocalStorage 都在 本地进行数据存储
  • SessionStorage 也有同源策略的限度,然而 SessionStorage 有一条更加严格的限度,SessionStorage只有在同一浏览器的同一窗口下才可能共享
  • LocalStorage 和 SessionStorage都不能被爬虫爬取

SessionStorage 的罕用 API:

// 保留数据到 sessionStorage
sessionStorage.setItem('key', 'value');

// 从 sessionStorage 获取数据
let data = sessionStorage.getItem('key');

// 从 sessionStorage 删除保留的数据
sessionStorage.removeItem('key');

// 从 sessionStorage 删除所有保留的数据
sessionStorage.clear();

// 获取某个索引的 Key
sessionStorage.key(index)

SessionStorage 的应用场景

  • 因为 SessionStorage 具备时效性,所以能够用来存储一些网站的游客登录的信息,还有长期的浏览记录的信息。当敞开网站之后,这些信息也就随之打消了。

如何实现浏览器内多个标签页之间的通信?

实现多个标签页之间的通信,实质上都是通过中介者模式来实现的。因为标签页之间没有方法间接通信,因而咱们能够找一个中介者,让标签页和中介者进行通信,而后让这个中介者来进行音讯的转发。通信办法如下:

  • 应用 websocket 协定,因为 websocket 协定能够实现服务器推送,所以服务器就能够用来当做这个中介者。标签页通过向服务器发送数据,而后由服务器向其余标签页推送转发。
  • 应用 ShareWorker 的形式,shareWorker 会在页面存在的生命周期内创立一个惟一的线程,并且开启多个页面也只会应用同一个线程。这个时候共享线程就能够充当中介者的角色。标签页间通过共享一个线程,而后通过这个共享的线程来实现数据的替换。
  • 应用 localStorage 的形式,咱们能够在一个标签页对 localStorage 的变动事件进行监听,而后当另一个标签页批改数据的时候,咱们就能够通过这个监听事件来获取到数据。这个时候 localStorage 对象就是充当的中介者的角色。
  • 应用 postMessage 办法,如果咱们可能取得对应标签页的援用,就能够应用 postMessage 办法,进行通信。

代码输入问题

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。

CSS 如何阻塞文档解析?

实践上,既然样式表不扭转 DOM 树,也就没有必要停下文档的解析期待它们。然而,存在一个问题,JavaScript 脚本执行时可能在文档的解析过程中申请款式信息,如果款式还没有加载和解析,脚本将失去谬误的值,显然这将会导致很多问题。所以如果浏览器尚未实现 CSSOM 的下载和构建,而咱们却想在此时运行脚本,那么浏览器将提早 JavaScript 脚本执行和文档的解析,直至其实现 CSSOM 的下载和构建。也就是说,在这种状况下,浏览器会先下载和构建 CSSOM,而后再执行 JavaScript,最初再持续文档的解析。

代码输入后果

function Foo(){Foo.a = function(){console.log(1);
    }
    this.a = function(){console.log(2)
    }
}

Foo.prototype.a = function(){console.log(3);
}

Foo.a = function(){console.log(4);
}

Foo.a();
let obj = new Foo();
obj.a();
Foo.a();

输入后果:4 2 1

解析:

  1. Foo.a() 这个是调用 Foo 函数的静态方法 a,尽管 Foo 中有优先级更高的属性办法 a,但 Foo 此时没有被调用,所以此时输入 Foo 的静态方法 a 的后果:4
  2. let obj = new Foo(); 应用了 new 办法调用了函数,返回了函数实例对象,此时 Foo 函数外部的属性办法初始化,原型链建设。
  3. obj.a() ; 调用 obj 实例上的办法 a,该实例上目前有两个 a 办法:一个是外部属性办法,另一个是原型上的办法。当这两者都存在时,首先查找 ownProperty,如果没有才去原型链上找,所以调用实例上的 a 输入:2
  4. Foo.a() ; 依据第 2 步可知 Foo 函数外部的属性办法已初始化,笼罩了同名的静态方法,所以输入:1

代码输入后果

console.log(1)

setTimeout(() => {console.log(2)
})

new Promise(resolve =>  {console.log(3)
  resolve(4)
}).then(d => console.log(d))

setTimeout(() => {console.log(5)
  new Promise(resolve =>  {resolve(6)
  }).then(d => console.log(d))
})

setTimeout(() => {console.log(7)
})

console.log(8)

输入后果如下:

1
3
8
4
2
5
6
7

代码执行过程如下:

  1. 首先执行 script 代码,打印出 1;
  2. 遇到第一个定时器,退出到宏工作队列;
  3. 遇到 Promise,执行代码,打印出 3,遇到 resolve,将其退出到微工作队列;
  4. 遇到第二个定时器,退出到宏工作队列;
  5. 遇到第三个定时器,退出到宏工作队列;
  6. 继续执行 script 代码,打印出 8,第一轮执行完结;
  7. 执行微工作队列,打印出第一个 Promise 的 resolve 后果:4;
  8. 开始执行宏工作队列,执行第一个定时器,打印出 2;
  9. 此时没有微工作,继续执行宏工作中的第二个定时器,首先打印出 5,遇到 Promise,首选打印出 6,遇到 resolve,将其退出到微工作队列;
  10. 执行微工作队列,打印出 6;
  11. 执行宏工作队列中的最初一个定时器,打印出 7。

浏览器渲染优化

(1)针对 JavaScript: JavaScript 既会阻塞 HTML 的解析,也会阻塞 CSS 的解析。因而咱们能够对 JavaScript 的加载形式进行扭转,来进行优化:

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

(2)body 两头尽量不要写 <script> 标签

(3)<script>标签的引入资源形式有三种,有一种就是咱们罕用的间接引入,还有两种就是应用 async 属性和 defer 属性来异步引入,两者都是去异步加载内部的 JS 文件,不会阻塞 DOM 的解析(尽量应用异步加载)。三者的区别如下:

  • script 立刻进行页面渲染去加载资源文件,当资源加载结束后立刻执行 js 代码,js 代码执行结束后持续渲染页面;
  • async 是在下载实现之后,立刻异步加载,加载好后立刻执行,多个带 async 属性的标签,不能保障加载的程序;
  • defer 是在下载实现之后,立刻异步加载。加载好后,如果 DOM 树还没构建好,则先等 DOM 树解析好再执行;如果 DOM 树曾经筹备好,则立刻执行。多个带 defer 属性的标签,依照程序执行。

(2)针对 CSS:应用 CSS 有三种形式:应用link、@import、内联款式,其中 link 和 @import 都是导入内部款式。它们之间的区别:

  • link:浏览器会派发一个新等线程 (HTTP 线程) 去加载资源文件,与此同时 GUI 渲染线程会持续向下渲染代码
  • @import:GUI 渲染线程会临时进行渲染,去服务器加载资源文件,资源文件没有返回之前不会持续渲染(妨碍浏览器渲染)
  • style:GUI 间接渲染

内部款式如果长时间没有加载结束,浏览器为了用户体验,会应用浏览器会默认款式,确保首次渲染的速度。所以 CSS 个别写在 headr 中,让浏览器尽快发送申请去获取 css 款式。

所以,在开发过程中,导入内部款式应用 link,而不必 @import。如果 css 少,尽可能采纳内嵌款式,间接写在 style 标签中。

(3)针对 DOM 树、CSSOM 树: 能够通过以下几种形式来缩小渲染的工夫:

  • HTML 文件的代码层级尽量不要太深
  • 应用语义化的标签,来防止不规范语义化的非凡解决
  • 缩小 CSSD 代码的层级,因为选择器是从左向右进行解析的

(4)缩小回流与重绘:

  • 操作 DOM 时,尽量在低层级的 DOM 节点进行操作
  • 不要应用 table 布局,一个小的改变可能会使整个 table 进行从新布局
  • 应用 CSS 的表达式
  • 不要频繁操作元素的款式,对于动态页面,能够批改类名,而不是款式。
  • 应用 absolute 或者 fixed,使元素脱离文档流,这样他们发生变化就不会影响其余元素
  • 防止频繁操作 DOM,能够创立一个文档片段documentFragment,在它下面利用所有 DOM 操作,最初再把它增加到文档中
  • 将元素先设置display: none,操作完结后再把它显示进去。因为在 display 属性为 none 的元素上进行的 DOM 操作不会引发回流和重绘。
  • 将 DOM 的多个读操作(或者写操作)放在一起,而不是读写操作穿插着写。这得益于 浏览器的渲染队列机制

浏览器针对页面的回流与重绘,进行了本身的优化——渲染队列

浏览器会将所有的回流、重绘的操作放在一个队列中,当队列中的操作到了肯定的数量或者到了肯定的工夫距离,浏览器就会对队列进行批处理。这样就会让屡次的回流、重绘变成一次回流重绘。

将多个读操作(或者写操作)放在一起,就会等所有的读操作进入队列之后执行,这样,本来应该是触发屡次回流,变成了只触发一次回流。

正文完
 0