关于前端:字节前端经典面试题附答案

8次阅读

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

有哪些可能引起前端平安的问题?

  • 跨站脚本 (Cross-Site Scripting, XSS): ⼀种代码注⼊⽅式, 为了与 CSS 辨别所以被称作 XSS。晚期常⻅于⽹络论坛, 起因是⽹站没有对⽤户的输⼊进⾏严格的限度, 使得攻击者能够将脚本上传到帖⼦让其余⼈浏览到有歹意脚本的⻚⾯, 其注⼊⽅式很简略包含但不限于 JavaScript / CSS / Flash 等;
  • iframe 的滥⽤: iframe 中的内容是由第三⽅来提供的,默认状况下他们不受管制,他们能够在 iframe 中运⾏ JavaScirpt 脚本、Flash 插件、弹出对话框等等,这可能会毁坏前端⽤户体验;
  • 跨站点申请伪造(Cross-Site Request Forgeries,CSRF): 指攻击者通过设置好的陷阱,强制对已实现认证的⽤户进⾏⾮预期的个⼈信息或设定信息等某些状态更新,属于被动攻打
  • 歹意第三⽅库: ⽆论是后端服务器应⽤还是前端应⽤开发,绝⼤少数时候都是在借助开发框架和各种类库进⾏疾速开发,⼀旦第三⽅库被植⼊恶意代码很容易引起平安问题。

写代码:实现函数可能深度克隆根本类型

浅克隆:

function shallowClone(obj) {let cloneObj = {};

  for (let i in obj) {cloneObj[i] = obj[i];
  }

  return cloneObj;
}

深克隆:

  • 思考根底类型
  • 援用类型

    • RegExp、Date、函数 不是 JSON 平安的
    • 会失落 constructor,所有的构造函数都指向 Object
    • 破解循环援用
function deepCopy(obj) {if (typeof obj === 'object') {var result = obj.constructor === Array ? [] : {};

    for (var i in obj) {result[i] = typeof obj[i] === 'object' ? deepCopy(obj[i]) : obj[i];
    }
  } else {var result = obj;}

  return result;
}

<script src=’xxx’’xxx’/> 内部 js 文件先加载还是 onload 先执行,为什么?

onload 是所以加载实现之后执行的

理解 this 嘛,bind,call,apply 具体指什么

它们都是函数的办法

call: Array.prototype.call(this, args1, args2]) apply: Array.prototype.apply(this, [args1, args2]):ES6 之前用来开展数组调用, foo.appy(null, []),ES6 之后应用 … 操作符

  • New 绑定 > 显示绑定 > 隐式绑定 > 默认绑定
  • 如果须要应用 bind 的柯里化和 apply 的数组解构,绑定到 null,尽可能应用 Object.create(null) 创立一个 DMZ 对象

四条规定:

  • 默认绑定,没有其余润饰(bind、apply、call),在非严格模式下定义指向全局对象,在严格模式下定义指向 undefined
function foo() {console.log(this.a); 
}

var a = 2;
foo();
  • 隐式绑定:调用地位是否有上下文对象,或者是否被某个对象领有或者蕴含,那么隐式绑定规定会把函数调用中的 this 绑定到这个上下文对象。而且,对象属性链只有上一层或者说最初一层在调用地位中起作用
function foo() {console.log(this.a);
}

var obj = {
  a: 2,
  foo: foo,
}

obj.foo(); // 2
  • 显示绑定:通过在函数上运行 call 和 apply,来显示的绑定 this
function foo() {console.log(this.a);
}

var obj = {a: 2};

foo.call(obj);

显示绑定之硬绑定

function foo(something) {console.log(this.a, something);

  return this.a + something;
}

function bind(fn, obj) {return function() {return fn.apply(obj, arguments);
  };
}

var obj = {a: 2}

var bar = bind(foo, obj);

New 绑定,new 调用函数会创立一个全新的对象,并将这个对象绑定到函数调用的 this。

  • New 绑定时,如果是 new 一个硬绑定函数,那么会用 new 新建的对象替换这个硬绑定 this,
function foo(a) {this.a = a;}

var bar = new foo(2);
console.log(bar.a)

代码输入后果

// a
function Foo () {getName = function () {console.log(1);
 }
 return this;
}
// b
Foo.getName = function () {console.log(2);
}
// c
Foo.prototype.getName = function () {console.log(3);
}
// d
var getName = function () {console.log(4);
}
// e
function getName () {console.log(5);
}

Foo.getName();           // 2
getName();               // 4
Foo().getName();         // 1
getName();               // 1 
new Foo.getName();       // 2
new Foo().getName();     // 3
new new Foo().getName(); // 3

输入后果:2 4 1 1 2 3 3

解析:

  1. Foo.getName(), Foo 为一个函数对象,对象都能够有属性,b 处定义 Foo 的 getName 属性为函数,输入 2;
  2. getName(), 这里看 d、e 处,d 为函数表达式,e 为函数申明,两者区别在于变量晋升,函数申明的 5 会被后边函数表达式的 4 笼罩;
  3. Foo().getName(), 这里要看 a 处,在 Foo 外部将全局的 getName 从新赋值为 console.log(1) 的函数,执行 Foo()返回 this,这个 this 指向 window,Foo().getName() 即为 window.getName(),输入 1;
  4. getName(), 下面 3 中,全局的 getName 曾经被从新赋值,所以这里仍然输入 1;
  5. new Foo.getName(), 这里等价于 new (Foo.getName()),先执行 Foo.getName(),输入 2,而后 new 一个实例;
  6. new Foo().getName(), 这 里等价于 (new Foo()).getName(), 先 new 一个 Foo 的实例,再执行这个实例的 getName 办法,然而这个实例自身没有这个办法,所以去原型链__protot__上边找,实例.protot === Foo.prototype,所以输入 3;
  7. new new Foo().getName(), 这里等价于 new (new Foo().getName()),如上述 6,先输入 3,而后 new 一个 new Foo().getName() 的实例。

一个 tcp 连贯能发几个 http 申请?

如果是 HTTP 1.0 版本协定,个别状况下,不反对长连贯,因而在每次申请发送结束之后,TCP 连贯即会断开,因而一个 TCP 发送一个 HTTP 申请,然而有一种状况能够将一条 TCP 连贯放弃在沉闷状态,那就是通过 Connection 和 Keep-Alive 首部,在申请头带上 Connection: Keep-Alive,并且能够通过 Keep-Alive 通用首部中指定的,用逗号分隔的选项调节 keep-alive 的行为,如果客户端和服务端都反对,那么其实也能够发送多条,不过此形式也有限度,能够关注《HTTP 权威指南》4.5.5 节对于 Keep-Alive 连贯的限度和规定。

而如果是 HTTP 1.1 版本协定,反对了长连贯,因而只有 TCP 连接不断开,便能够始终发送 HTTP 申请,继续一直,没有下限;同样,如果是 HTTP 2.0 版本协定,反对多用复用,一个 TCP 连贯是能够并发多个 HTTP 申请的,同样也是反对长连贯,因而只有一直开 TCP 的连贯,HTTP 申请数也是能够没有下限地继续发送

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

什么是同源策略

跨域问题其实就是浏览器的同源策略造成的。

同源策略限度了从同一个源加载的文档或脚本如何与另一个源的资源进行交互。这是浏览器的一个用于隔离潜在歹意文件的重要的平安机制。同源指的是:协定 端口号 域名 必须统一。

同源策略:protocol(协定)、domain(域名)、port(端口)三者必须统一。

同源政策次要限度了三个方面:

  • 以后域下的 js 脚本不可能拜访其余域下的 cookie、localStorage 和 indexDB。
  • 以后域下的 js 脚本不可能操作拜访操作其余域下的 DOM。
  • 以后域下 ajax 无奈发送跨域申请。

同源政策的目标次要是为了保障用户的信息安全,它只是对 js 脚本的一种限度,并不是对浏览器的限度,对于个别的 img、或者 script 脚本申请都不会有跨域的限度,这是因为这些操作都不会通过响应后果来进行可能呈现平安问题的操作。

说下对 JS 的理解吧

是基于原型的动静语言,次要独特个性有 this、原型和原型链。

JS 严格意义上来说分为:语言规范局部(ECMAScript)+ 宿主环境局部

语言规范局部

2015 年公布 ES6,引入诸多新个性使得可能编写大型项目变成可能,规范自 2015 之后以年号代号,每年一更

宿主环境局部

  • 在浏览器宿主环境包含 DOM + BOM 等
  • 在 Node,宿主环境包含一些文件、数据库、网络、与操作系统的交互等

React 17 带来了哪些扭转

最重要的是以下三点:

  • 新的 JSX 转换逻辑
  • 事件零碎重构
  • Lane 模型 的引入

1. 重构 JSX 转换逻辑

在过来,如果咱们在 React 我的项目中写入上面这样的代码:

function MyComponent() {return <p> 这是我的组件 </p>}

React 是会报错的,起因是 React 中对 JSX 代码的转换依赖的是 React.createElement 这个函数。因而凡是咱们在代码中蕴含了 JSX,那么就必须在文件中引入 React,像上面这样:

import React from 'react';
function MyComponent() {return <p> 这是我的组件 </p>}

React 17 则容许咱们在不引入 React 的状况下间接应用 JSX。这是因为在 React 17 中,编译器会主动帮咱们引入 JSX 的解析器,也就是说像上面这样一段逻辑:

function MyComponent() {return <p> 这是我的组件 </p>}

会被编译器转换成这个样子:

import {jsx as _jsx} from 'react/jsx-runtime';
function MyComponent() {return _jsx('p', { children: '这是我的组件'});
}

react/jsx-runtime 中的 JSX 解析器将取代 React.createElement 实现 JSX 的编译工作,这个过程对开发者而言是自动化、无感知的。因而,新的 JSX 转换逻辑带来的最显著的扭转就是升高了开发者的学习老本。

react/jsx-runtime 中的 JSX 解析器看上去仿佛在调用姿态上和 React.createElement 区别不大,那么它是否只是 React.createElement 换了个马甲呢?当然不是,它在外部实现了 React.createElement 无奈做到的性能优化和简化。在肯定状况下,它可能会稍微改善编译输入内容的大小

2. 事件零碎重构

事件零碎在 React 17 中的重构要从以下两个方面来看:

  • 卸掉历史包袱
  • 拥抱新的潮流

2.1 卸掉历史包袱:放弃利用 document 来做事件的中心化管控

React 16.13.x 版本中的事件零碎会通过将所有事件冒泡到 document 来实现对事件的中心化管控

这样的做法尽管看上去曾经足够奇妙,但依然有它不聪慧的中央——document 是整个文档树的根节点,操作 document 带来的影响范畴切实是太大了,这将会使事件变得更加不可控

在 React 17 中,React 团队终于侧面解决了这个问题:事件的中心化管控不会再全副依赖 document,管控相干的逻辑被转移到了每个 React 组件本人的容器 DOM 节点中。比如说咱们在 ID 为 root 的 DOM 节点下挂载了一个 React 组件,像上面代码这样:

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

那么事件管控相干的逻辑就会被装置到 root 节点 下来。这样一来,React 组件就可能本人玩本人的,再也无奈对全局的事件流构成威胁了

2.2 拥抱新的潮流:放弃事件池

在 React 17 之前,合成事件对象会被放进一个叫作“事件池”的中央对立治理。这样做的目标是可能实现事件对象的复用,进而进步性能:每当事件处理函数执行结束后,其对应的合成事件对象外部的所有属性都会被置空,意在为下一次被复用做筹备。这也就意味着事件逻辑一旦执行结束,咱们就拿不到事件对象了,React 官网给出的这个例子就很能阐明问题,请看上面这个代码

function handleChange(e) {
  // This won't work because the event object gets reused.
  setTimeout(() => {console.log(e.target.value); // Too late!
  }, 100);
}

异步执行的 setTimeout 回调会在 handleChange 这个事件处理函数执行结束后执行,因而它拿不到想要的那个事件对象 e

要想拿到指标事件对象,必须显式地通知 React——我永远须要它,也就是调用 e.persist() 函数,像上面这样:

function handleChange(e) {
  // Prevents React from resetting its properties:
  e.persist();
  setTimeout(() => {console.log(e.target.value); // Works
  }, 100);
}

在 React 17 中,咱们不须要 e.persist(),也能够随时随地拜访咱们想要的事件对象。

3. Lane 模型的引入

初学 React 源码的同学由此可能会很天然地认为:优先级就应该是用 Lane 来解决的。但事实上,React 16 中解决优先级采纳的是 expirationTime 模型

expirationTime 模型应用 expirationTime(一个工夫长度)来形容工作的优先级;而 Lane 模型 则应用 二进制数来示意工作的优先级

lane 模型 通过将不同优先级赋值给一个位,通过 31 位的位运算 来操作优先级。

Lane 模型 提供了一个新的优先级排序的思路,绝对于 expirationTime 来说,它对优先级的解决会更细腻,可能笼罩更多的边界条件。

浏览器的渲染过程

浏览器渲染次要有以下步骤:

  • 首先解析收到的文档,依据文档定义构建一棵 DOM 树,DOM 树是由 DOM 元素及属性节点组成的。
  • 而后对 CSS 进行解析,生成 CSSOM 规定树。
  • 依据 DOM 树和 CSSOM 规定树构建渲染树。渲染树的节点被称为渲染对象,渲染对象是一个蕴含有色彩和大小等属性的矩形,渲染对象和 DOM 元素绝对应,但这种对应关系不是一对一的,不可见的 DOM 元素不会被插入渲染树。还有一些 DOM 元素对应几个可见对象,它们个别是一些具备简单构造的元素,无奈用一个矩形来形容。
  • 当渲染对象被创立并增加到树中,它们并没有地位和大小,所以当浏览器生成渲染树当前,就会依据渲染树来进行布局(也能够叫做回流)。这一阶段浏览器要做的事件是要弄清楚各个节点在页面中的确切地位和大小。通常这一行为也被称为“主动重排”。
  • 布局阶段完结后是绘制阶段,遍历渲染树并调用渲染对象的 paint 办法将它们的内容显示在屏幕上,绘制应用 UI 根底组件。

大抵过程如图所示:

留神: 这个过程是逐渐实现的,为了更好的用户体验,渲染引擎将会尽可能早的将内容出现到屏幕上,并不会等到所有的 html 都解析实现之后再去构建和布局 render 树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。

过程与线程的概念

从实质上说,过程和线程都是 CPU 工作工夫片的一个形容:

  • 过程形容了 CPU 在运行指令及加载和保留上下文所需的工夫,放在利用上来说就代表了一个程序。
  • 线程是过程中的更小单位,形容了执行一段指令所需的工夫。

过程是资源分配的最小单位,线程是 CPU 调度的最小单位。

一个过程就是一个程序的运行实例。具体解释就是,启动一个程序的时候,操作系统会为该程序创立一块内存,用来寄存代码、运行中的数据和一个执行工作的主线程,咱们把这样的一个运行环境叫 过程 过程是运行在虚拟内存上的,虚拟内存是用来解决用户对硬件资源的有限需要和无限的硬件资源之间的矛盾的。从操作系统角度来看,虚拟内存即交换文件;从处理器角度看,虚拟内存即虚拟地址空间。

如果程序很多时,内存可能会不够,操作系统为每个过程提供一套独立的虚拟地址空间,从而使得同一块物理内存在不同的过程中能够对应到不同或雷同的虚拟地址,变相的减少了程序能够应用的内存。

过程和线程之间的关系有以下四个特点:

(1)过程中的任意一线程执行出错,都会导致整个过程的解体。

(2)线程之间共享过程中的数据。

(3)当一个过程敞开之后,操作系统会回收过程所占用的内存, 当一个过程退出时,操作系统会回收该过程所申请的所有资源;即便其中任意线程因为操作不当导致内存透露,当过程退出时,这些内存也会被正确回收。

(4)过程之间的内容互相隔离。 过程隔离就是为了使操作系统中的过程互不烦扰,每一个过程只能拜访本人占有的数据,也就避免出现过程 A 写入数据到过程 B 的状况。正是因为过程之间的数据是严格隔离的,所以一个过程如果解体了,或者挂起了,是不会影响到其余过程的。如果过程之间须要进行数据的通信,这时候,就须要应用用于过程间通信的机制了。

Chrome 浏览器的架构图:从图中能够看出,最新的 Chrome 浏览器包含:

  • 1 个浏览器主过程
  • 1 个 GPU 过程
  • 1 个网络过程
  • 多个渲染过程
  • 多个插件过程

这些过程的性能:

  • 浏览器过程:次要负责界面显示、用户交互、子过程治理,同时提供存储等性能。
  • 渲染过程:外围工作是将 HTML、CSS 和 JavaScript 转换为用户能够与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该过程中,默认状况下,Chrome 会为每个 Tab 标签创立一个渲染过程。出于平安思考,渲染过程都是运行在沙箱模式下。
  • GPU 过程:其实,GPU 的应用初衷是为了实现 3D CSS 的成果,只是随后网页、Chrome 的 UI 界面都抉择采纳 GPU 来绘制,这使得 GPU 成为浏览器广泛的需要。最初,Chrome 在其多过程架构上也引入了 GPU 过程。
  • 网络过程:次要负责页面的网络资源加载,之前是作为一个模块运行在浏览器过程外面的,直至最近才独立进去,成为一个独自的过程。
  • 插件过程:次要是负责插件的运行,因插件易解体,所以须要通过插件过程来隔离,以保障插件过程解体不会对浏览器和页面造成影响。

所以,关上一个网页,起码须要四个过程:1 个网络过程、1 个浏览器过程、1 个 GPU 过程以及 1 个渲染过程。如果关上的页面有运行插件的话,还须要再加上 1 个插件过程。

尽管多过程模型晋升了浏览器的稳定性、流畅性和安全性,但同样不可避免地带来了一些问题:

  • 更高的资源占用:因为每个过程都会蕴含公共根底构造的正本(如 JavaScript 运行环境),这就意味着浏览器会耗费更多的内存资源。
  • 更简单的体系架构:浏览器各模块之间耦合性高、扩展性差等问题,会导致当初的架构曾经很难适应新的需要了。

对对象与数组的解构的了解

解构是 ES6 提供的一种新的提取数据的模式,这种模式可能从对象或数组里有针对性地拿到想要的数值。1)数组的解构 在解构数组时,以元素的地位为匹配条件来提取想要的数据的:

const [a, b, c] = [1, 2, 3]

最终,a、b、c 别离被赋予了数组第 0、1、2 个索引位的值:

数组里的 0、1、2 索引位的元素值,精准地被映射到了左侧的第 0、1、2 个变量里去,这就是数组解构的工作模式。还能够通过给左侧变量数组设置空占位的形式,实现对数组中某几个元素的精准提取:

const [a,,c] = [1,2,3]

通过把两头位留空,能够顺利地把数组第一位和最初一位的值赋给 a、c 两个变量:

2)对象的解构 对象解构比数组构造略微简单一些,也更显弱小。在解构对象时,是以属性的名称为匹配条件,来提取想要的数据的。当初定义一个对象:

const stu = {
  name: 'Bob',
  age: 24
}

如果想要解构它的两个自有属性,能够这样:

const {name, age} = stu

这样就失去了 name 和 age 两个和 stu 平级的变量:

留神,对象解构严格以属性名作为定位根据,所以就算调换了 name 和 age 的地位,后果也是一样的:

const {age, name} = stu

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

对节流与防抖的了解

  • 函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则从新计时。这能够应用在一些点击申请的事件上,防止因为用户的屡次点击向后端发送屡次申请。
  • 函数节流是指规定一个单位工夫,在这个单位工夫内,只能有一次触发事件的回调函数执行,如果在同一个单位工夫内某事件被触发屡次,只有一次能失效。节流能够应用在 scroll 函数的事件监听上,通过事件节流来升高事件调用的频率。

防抖函数的利用场景:

  • 按钮提交场景:防⽌屡次提交按钮,只执⾏最初提交的⼀次
  • 服务端验证场景:表单验证须要服务端配合,只执⾏⼀段间断的输⼊事件的最初⼀次,还有搜寻联想词性能相似⽣存环境请⽤ lodash.debounce

节流函数的适⽤场景:

  • 拖拽场景:固定工夫内只执⾏⼀次,防⽌超⾼频次触发地位变动
  • 缩放场景:监控浏览器 resize
  • 动画场景:防止短时间内屡次触发动画引起性能问题

Number() 的存储空间是多大?如果后盾发送了一个超过最大本人的数字怎么办

Math.pow(2, 53),53 为有效数字,会产生截断,等于 JS 能反对的最大数字。

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

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

零碎中的资源能够分为两类:

  • 可剥夺资源,是指某过程在取得这类资源后,该资源能够再被其余过程或零碎剥夺,CPU 和主存均属于可剥夺性资源;
  • 不可剥夺资源,当零碎把这类资源分配给某过程后,再不能强行发出,只能在过程用完后自行开释,如磁带机、打印机等。

产生死锁的起因:

(1)竞争资源

  • 产生死锁中的竞争资源之一指的是 竞争不可剥夺资源(例如:零碎中只有一台打印机,可供过程 P1 应用,假设 P1 已占用了打印机,若 P2 持续要求打印机打印将阻塞)
  • 产生死锁中的竞争资源另外一种资源指的是 竞争长期资源(长期资源包含硬件中断、信号、音讯、缓冲区内的音讯等),通常音讯通信程序进行不当,则会产生死锁

(2)过程间推动程序非法

若 P1 放弃了资源 R1,P2 放弃了资源 R2,零碎处于不平安状态,因为这两个过程再向前推动,便可能产生死锁。例如,当 P1 运行到 P1:Request(R2)时,将因 R2 已被 P2 占用而阻塞;当 P2 运行到 P2:Request(R1)时,也将因 R1 已被 P1 占用而阻塞,于是产生过程死锁

产生死锁的必要条件:

  • 互斥条件:过程要求对所调配的资源进行排它性管制,即在一段时间内某资源仅为一过程所占用。
  • 申请和放弃条件:当过程因申请资源而阻塞时,对已取得的资源放弃不放。
  • 不剥夺条件:过程已取得的资源在未应用完之前,不能剥夺,只能在应用完时由本人开释。
  • 环路期待条件:在产生死锁时,必然存在一个过程——资源的环形链。

预防死锁的办法:

  • 资源一次性调配:一次性调配所有资源,这样就不会再有申请了(毁坏申请条件)
  • 只有有一个资源得不到调配,也不给这个过程调配其余的资源(毁坏请放弃条件)
  • 可剥夺资源:即当某过程取得了局部资源,但得不到其它资源,则开释已占有的资源(毁坏不可剥夺条件)
  • 资源有序调配法:零碎给每类资源赋予一个编号,每一个过程按编号递增的程序申请资源,开释则相同(毁坏环路期待条件)

代码输入后果

const async1 = async () => {console.log('async1');
  setTimeout(() => {console.log('timer1')
  }, 2000)
  await new Promise(resolve => {console.log('promise1')
  })
  console.log('async1 end')
  return 'async1 success'
} 
console.log('script start');
async1().then(res => console.log(res));
console.log('script end');
Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .catch(4)
  .then(res => console.log(res))
setTimeout(() => {console.log('timer2')
}, 1000)

输入后果如下:

script start
async1
promise1
script end
1
timer2
timer1

代码的执行过程如下:

  1. 首先执行同步带吗,打印出 script start;
  2. 遇到定时器 timer1 将其退出宏工作队列;
  3. 之后是执行 Promise,打印出 promise1,因为 Promise 没有返回值,所以前面的代码不会执行;
  4. 而后执行同步代码,打印出 script end;
  5. 继续执行上面的 Promise,.then 和.catch 冀望参数是一个函数,这里传入的是一个数字,因而就会产生值浸透,将 resolve(1)的值传到最初一个 then,间接打印出 1;
  6. 遇到第二个定时器,将其退出到微工作队列,执行微工作队列,按程序顺次执行两个定时器,然而因为定时器工夫的起因,会在两秒后先打印出 timer2,在四秒后打印出 timer1。

判断数组的形式有哪些

  • 通过 Object.prototype.toString.call()做判断
Object.prototype.toString.call(obj).slice(8,-1) === 'Array';
  • 通过原型链做判断
obj.__proto__ === Array.prototype;
  • 通过 ES6 的 Array.isArray()做判断
Array.isArrray(obj);
  • 通过 instanceof 做判断
obj instanceof Array
  • 通过 Array.prototype.isPrototypeOf
Array.prototype.isPrototypeOf(obj)

JS 隐式转换,显示转换

个别非根底类型进行转换时会先调用 valueOf,如果 valueOf 无奈返回根本类型值,就会调用 toString

字符串和数字

  • “+” 操作符,如果有一个为字符串,那么都转化到字符串而后执行字符串拼接
  • “-” 操作符,转换为数字,相减 (-a, a * 1 a/1) 都能进行隐式强制类型转换
[] + {} 和 {} + []

布尔值到数字

  • 1 + true = 2
  • 1 + false = 1

转换为布尔值

  • for 中第二个
  • while
  • if
  • 三元表达式
  • ||(逻辑或)&&(逻辑与)右边的操作数

符号

  • 不能被转换为数字
  • 能被转换为布尔值(都是 true)
  • 能够被转换成字符串 “Symbol(cool)”

宽松相等和严格相等

宽松相等容许进行强制类型转换,而严格相等不容许

字符串与数字

转换为数字而后比拟

其余类型与布尔类型

  • 先把布尔类型转换为数字,而后持续进行比拟

对象与非对象

  • 执行对象的 ToPrimitive(对象)而后持续进行比拟

假值列表

  • undefined
  • null
  • false
  • +0, -0, NaN
  • “”

什么是物理像素,逻辑像素和像素密度,为什么在挪动端开发时须要用到 @3x, @2x 这种图片?

以 iPhone XS 为例,当写 CSS 代码时,针对于单位 px,其宽度为 414px & 896px,也就是说当赋予一个 DIV 元素宽度为 414px,这个 DIV 就会填满手机的宽度;

而如果有一把尺子来理论测量这部手机的物理像素,理论为 1242*2688 物理像素;通过计算可知,1242/414=3,也就是说,在单边上,一个逻辑像素 = 3 个物理像素,就说这个屏幕的像素密度为 3,也就是常说的 3 倍屏。

对于图片来说,为了保障其不失真,1 个图片像素至多要对应一个物理像素,如果原始图片是 500300 像素,那么在 3 倍屏上就要放一个 1500900 像素的图片能力保障 1 个物理像素至多对应一个图片像素,能力不失真。当然,也能够针对所有屏幕,都只提供最高清图片。尽管低密度屏幕用不到那么多图片像素,而且会因为下载多余的像素造成带宽节约和下载提早,但从后果上说能保障图片在所有屏幕上都不会失真。

还能够应用 CSS 媒体查问来判断不同的像素密度,从而抉择不同的图片:

my-image {background: (low.png); }
@media only screen and (min-device-pixel-ratio: 1.5) {#my-image { background: (high.png); }
}

正文完
 0