关于前端:前端经典面试题有答案

3次阅读

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

代码输入后果

var a = 10
var obj = {
  a: 20,
  say: () => {console.log(this.a)
  }
}
obj.say() 

var anotherObj = {a: 30} 
obj.say.apply(anotherObj) 

输入后果:10 10

我么晓得,箭头函数时不绑定 this 的,它的 this 来自原其父级所处的上下文,所以首先会打印全局中的 a 的值 10。前面尽管让 say 办法指向了另外一个对象,然而仍不能扭转箭头函数的个性,它的 this 依然是指向全局的,所以依旧会输入 10。

然而,如果是一般函数,那么就会有齐全不一样的后果:

var a = 10  
var obj = {  
  a: 20,  
  say(){console.log(this.a)  
  }  
}  
obj.say()   
var anotherObj={a:30}   
obj.say.apply(anotherObj)

输入后果:20 30

这时,say 办法中的 this 就会指向他所在的对象,输入其中的 a 的值。

Compositon api

Composition API也叫组合式 API,是 Vue3.x 的新个性。

通过创立 Vue 组件,咱们能够将接口的可重复部分及其性能提取到可重用的代码段中。仅此一项就能够使咱们的应用程序在可维护性和灵活性方面走得更远。然而,咱们的教训曾经证实,光靠这一点可能是不够的,尤其是当你的应用程序变得十分大的时候——想想几百个组件。在解决如此大的应用程序时,共享和重用代码变得尤为重要

  • Vue2.0 中,随着性能的减少,组件变得越来越简单,越来越难保护,而难以保护的根本原因是 Vue 的 API 设计迫使开发者应用 watch,computed,methods 选项组织代码,而不是理论的业务逻辑。
  • 另外 Vue2.0 短少一种较为简洁的低成本的机制来实现逻辑复用,尽管能够 minxis 实现逻辑复用,然而当 mixin 变多的时候,会使得难以找到对应的 data、computed 或者 method 来源于哪个mixin,使得类型推断难以进行。
  • 所以 Composition API 的呈现,次要是也是为了解决 Option API 带来的问题,第一个是代码组织问题,Compostion API能够让开发者依据业务逻辑组织本人的代码,让代码具备更好的可读性和可扩展性,也就是说当下一个开发者接触这一段不是他本人写的代码时,他能够更好的利用代码的组织反推出理论的业务逻辑,或者依据业务逻辑更好的了解代码。
  • 第二个是实现代码的逻辑提取与复用,当然 mixin 也能够实现逻辑提取与复用,然而像后面所说的,多个 mixin 作用在同一个组件时,很难看出 property 是来源于哪个 mixin,起源不分明,另外,多个mixinproperty存在变量命名抵触的危险。而 Composition API 刚好解决了这两个问题。

艰深的讲:

没有 Composition API 之前 vue 相干业务的代码须要配置到 option 的特定的区域,中小型我的项目是没有问题的,然而在大型项目中会导致前期的维护性比较复杂,同时代码可复用性不高。Vue3.x 中的 composition-api 就是为了解决这个问题而生的

compositon api 提供了以下几个函数:

  • setup
  • ref
  • reactive
  • watchEffect
  • watch
  • computed
  • toRefs
  • 生命周期的hooks

都说 Composition API 与 React Hook 很像,说说区别

从 React Hook 的实现角度看,React Hook 是依据 useState 调用的程序来确定下一次重渲染时的 state 是来源于哪个 useState,所以呈现了以下限度

  • 不能在循环、条件、嵌套函数中调用 Hook
  • 必须确保总是在你的 React 函数的顶层调用 Hook
  • useEffect、useMemo等函数必须手动确定依赖关系

而 Composition API 是基于 Vue 的响应式零碎实现的,与 React Hook 的相比

  • 申明在 setup 函数内,一次组件实例化只调用一次setup,而 React Hook 每次重渲染都须要调用 Hook,使得 React 的 GC 比 Vue 更有压力,性能也绝对于 Vue 来说也较慢
  • Compositon API的调用不须要顾虑调用程序,也能够在循环、条件、嵌套函数中应用
  • 响应式零碎主动实现了依赖收集,进而组件的局部的性能优化由 Vue 外部本人实现,而 React Hook 须要手动传入依赖,而且必须必须保障依赖的程序,让 useEffectuseMemo 等函数正确的捕捉依赖变量,否则会因为依赖不正确使得组件性能降落。

尽管 Compositon API 看起来比 React Hook 好用,然而其设计思维也是借鉴 React Hook 的。

垃圾回收

  • 对于在 JavaScript 中的字符串,对象,数组是没有固定大小的,只有当对他们进行动态分配存储时,解释器就会分配内存来存储这些数据,当 JavaScript 的解释器耗费完零碎中所有可用的内存时,就会造成零碎解体。
  • 内存透露,在某些状况下,不再应用到的变量所占用内存没有及时开释,导致程序运行中,内存越占越大,极其状况下能够导致系统解体,服务器宕机。
  • JavaScript 有本人的一套垃圾回收机制,JavaScript 的解释器能够检测到什么时候程序不再应用这个对象了(数据),就会把它所占用的内存开释掉。
  • 针对 JavaScript 的来及回收机制有以下两种办法(罕用):标记革除,援用计数
  • 标记革除

v8 的垃圾回收机制基于分代回收机制,这个机制又基于世代假说,这个假说有两个特点,一是新生的对象容易早死,另一个是不死的对象会活得更久。基于这个假说,v8 引擎将内存分为了新生代和老生代。

  • 新创建的对象或者只经验过一次的垃圾回收的对象被称为新生代。经验过屡次垃圾回收的对象被称为老生代。
  • 新生代被分为 From 和 To 两个空间,To 个别是闲置的。当 From 空间满了的时候会执行 Scavenge 算法进行垃圾回收。当咱们执行垃圾回收算法的时候应用逻辑将会进行,等垃圾回收完结后再继续执行。

这个算法分为三步:

  • 首先查看 From 空间的存活对象,如果对象存活则判断对象是否满足降职到老生代的条件,如果满足条件则降职到老生代。如果不满足条件则挪动 To 空间。
  • 如果对象不存活,则开释对象的空间。
  • 最初将 From 空间和 To 空间角色进行替换。

新生代对象降职到老生代有两个条件:

  • 第一个是判断是对象否曾经通过一次 Scavenge 回收。若经验过,则将对象从 From 空间复制到老生代中;若没有经验,则复制到 To 空间。
  • 第二个是 To 空间的内存应用占比是否超过限度。当对象从 From 空间复制到 To 空间时,若 To 空间应用超过 25%,则对象间接降职到老生代中。设置 25% 的起因次要是因为算法完结后,两个空间完结后会替换地位,如果 To 空间的内存太小,会影响后续的内存调配。

老生代采纳了标记革除法和标记压缩法。标记革除法首先会对内存中存活的对象进行标记,标记完结后革除掉那些没有标记的对象。因为标记革除后会造成很多的内存碎片,不便于前面的内存调配。所以了解决内存碎片的问题引入了标记压缩法。

因为在进行垃圾回收的时候会暂停利用的逻辑,对于新生代办法因为内存小,每次进展的工夫不会太长,但对于老生代来说每次垃圾回收的工夫长,进展会造成很大的影响。为了解决这个问题 V8 引入了增量标记的办法,将一次进展进行的过程分为了多步,每次执行完一小步就让运行逻辑执行一会,就这样交替运行

Proxy 代理

proxy 在指标对象的外层搭建了一层拦挡,外界对指标对象的某些操作,必须通过这层拦挡

var proxy = new Proxy(target, handler);

new Proxy()示意生成一个 Proxy 实例,target参数示意所要拦挡的指标对象,handler参数也是一个对象,用来定制拦挡行为

var target = {name: 'poetries'};
 var logHandler = {get: function(target, key) {console.log(`${key} 被读取 `);
     return target[key];
   },
   set: function(target, key, value) {console.log(`${key} 被设置为 ${value}`);
     target[key] = value;
   }
 }
 var targetWithLog = new Proxy(target, logHandler);

 targetWithLog.name; // 控制台输入:name 被读取
 targetWithLog.name = 'others'; // 控制台输入:name 被设置为 others

 console.log(target.name); // 控制台输入: others
  • targetWithLog 读取属性的值时,实际上执行的是 logHandler.get:在控制台输入信息,并且读取被代理对象 target 的属性。
  • targetWithLog 设置属性值时,实际上执行的是 logHandler.set:在控制台输入信息,并且设置被代理对象 target 的属性的值
// 因为拦挡函数总是返回 35,所以拜访任何属性都失去 35
var proxy = new Proxy({}, {get: function(target, property) {return 35;}
});

proxy.time // 35
proxy.name // 35
proxy.title // 35

Proxy 实例也能够作为其余对象的原型对象

var proxy = new Proxy({}, {get: function(target, property) {return 35;}
});

let obj = Object.create(proxy);
obj.time // 35

proxy对象是 obj 对象的原型,obj对象自身并没有 time 属性,所以依据原型链,会在 proxy 对象上读取该属性,导致被拦挡

Proxy 的作用

对于代理模式 Proxy 的作用次要体现在三个方面

  • 拦挡和监督内部对对象的拜访
  • 升高函数或类的复杂度
  • 在简单操作前对操作进行校验或对所需资源进行治理

Proxy 所能代理的范畴 –handler

实际上 handler 自身就是 ES6 所新设计的一个对象. 它的作用就是用来 自定义代理对象的各种可代理操作。它自身一共有 13 中办法, 每种办法都能够代理一种操作. 其 13 种办法如下

// 在读取代理对象的原型时触发该操作,比方在执行 Object.getPrototypeOf(proxy) 时。handler.getPrototypeOf()

// 在设置代理对象的原型时触发该操作,比方在执行 Object.setPrototypeOf(proxy, null) 时。handler.setPrototypeOf()


// 在判断一个代理对象是否是可扩大时触发该操作,比方在执行 Object.isExtensible(proxy) 时。handler.isExtensible()


// 在让一个代理对象不可扩大时触发该操作,比方在执行 Object.preventExtensions(proxy) 时。handler.preventExtensions()

// 在获取代理对象某个属性的属性形容时触发该操作,比方在执行 Object.getOwnPropertyDescriptor(proxy, "foo") 时。handler.getOwnPropertyDescriptor()


// 在定义代理对象某个属性时的属性形容时触发该操作,比方在执行 Object.defineProperty(proxy, "foo", {}) 时。andler.defineProperty()


// 在判断代理对象是否领有某个属性时触发该操作,比方在执行 "foo" in proxy 时。handler.has()

// 在读取代理对象的某个属性时触发该操作,比方在执行 proxy.foo 时。handler.get()


// 在给代理对象的某个属性赋值时触发该操作,比方在执行 proxy.foo = 1 时。handler.set()

// 在删除代理对象的某个属性时触发该操作,比方在执行 delete proxy.foo 时。handler.deleteProperty()

// 在获取代理对象的所有属性键时触发该操作,比方在执行 Object.getOwnPropertyNames(proxy) 时。handler.ownKeys()

// 在调用一个指标对象为函数的代理对象时触发该操作,比方在执行 proxy() 时。handler.apply()


// 在给一个指标对象为构造函数的代理对象结构实例时触发该操作,比方在执行 new proxy() 时。handler.construct()

为何 Proxy 不能被 Polyfill

  • 如 class 能够用 function 模仿;promise能够用 callback 模仿
  • 然而 proxy 不能用 Object.defineProperty 模仿

目前谷歌的 polyfill 只能实现局部的性能,如 get、set https://github.com/GoogleChro…

// commonJS require
const proxyPolyfill = require('proxy-polyfill/src/proxy')();

// Your environment may also support transparent rewriting of commonJS to ES6:
import ProxyPolyfillBuilder from 'proxy-polyfill/src/proxy';
const proxyPolyfill = ProxyPolyfillBuilder();

// Then use...
const myProxy = new proxyPolyfill(...);

列举几个 css 中可继承和不可继承的元素

  • 不可继承的:display、margin、border、padding、background、height、min-height、max-height、width、min-width、max-width、overflow、position、left、right、top、bottom、z-index、float、clear、table-layout、vertical-align
  • 所有元素可继承:visibilitycursor
  • 内联元素可继承:letter-spacing、word-spacing、white-space、line-height、color、font、font-family、font-size、font-style、font-variant、font-weight、text-decoration、text-transform、direction
  • 终端块状元素可继承:text-indent 和 text-align
  • 列表元素可继承:list-style、list-style-type、list-style-position、list-style-image`。

transition 和 animation 的区别

Animationtransition 大部分属性是雷同的,他们都是随工夫扭转元素的属性值,他们的次要区别是 transition 须要触发一个事件能力扭转属性,而 animation 不须要触发任何事件的状况下才会随工夫扭转属性值,并且 transition 为 2 帧,从 from .... to,而animation 能够一帧一帧的

OSI 七层模型

ISO为了更好的使网络应用更为遍及,推出了 OSI 参考模型。

(1)应用层

OSI参考模型中最靠近用户的一层,是为计算机用户提供利用接口,也为用户间接提供各种网络服务。咱们常见应用层的网络服务协定有:HTTPHTTPSFTPPOP3SMTP等。

  • 在客户端与服务器中常常会有数据的申请,这个时候就是会用到 http(hyper text transfer protocol)(超文本传输协定) 或者https. 在后端设计数据接口时,咱们经常应用到这个协定。
  • FTP是文件传输协定,在开发过程中,集体并没有波及到,然而我想,在一些资源网站,比方 百度网盘` 迅雷 ` 应该是基于此协定的。
  • SMTPsimple mail transfer protocol(简略邮件传输协定)。在一个我的项目中,在用户邮箱验证码登录的性能时,应用到了这个协定。

(2)表示层

表示层提供各种用于应用层数据的编码和转换性能, 确保一个零碎的应用层发送的数据能被另一个零碎的应用层辨认。如果必要,该层可提供一种规范示意模式,用于将计算机外部的多种数据格式转换成通信中采纳的规范示意模式。数据压缩和加密也是表示层可提供的转换性能之一。

在我的项目开发中,为了不便数据传输,能够应用 base64 对数据进行编解码。如果按性能来划分,base64应该是工作在表示层。

(3)会话层

会话层就是负责建设、治理和终止表示层实体之间的通信会话。该层的通信由不同设施中的应用程序之间的服务申请和响应组成。

(4)传输层

传输层建设了主机端到端的链接,传输层的作用是为下层协定提供端到端的牢靠和通明的数据传输服务,包含解决差错控制和流量管制等问题。该层向高层屏蔽了上层数据通信的细节,使高层用户看到的只是在两个传输实体间的一条主机到主机的、可由用户管制和设定的、牢靠的数据通路。咱们通常说的,TCP UDP就是在这一层。端口号既是这里的“端”。

(5)网络层

本层通过 IP 寻址来建设两个节点之间的连贯,为源端的运输层送来的分组,抉择适合的路由和替换节点,正确无误地依照地址传送给目标端的运输层。就是通常说的 IP 层。这一层就是咱们常常说的 IP 协定层。IP协定是 Internet 的根底。咱们能够这样了解,网络层规定了数据包的传输路线,而传输层则规定了数据包的传输方式。

(6)数据链路层

将比特组合成字节, 再将字节组合成帧, 应用链路层地址 (以太网应用 MAC 地址)来拜访介质, 并进行过错检测。
网络层与数据链路层的比照,通过下面的形容,咱们或者能够这样了解,网络层是布局了数据包的传输路线,而数据链路层就是传输路线。不过,在数据链路层上还减少了差错控制的性能。

(7)物理层

理论最终信号的传输是通过物理层实现的。通过物理介质传输比特流。规定了电平、速度和电缆针脚。罕用设施有(各种物理设施)集线器、中继器、调制解调器、网线、双绞线、同轴电缆。这些都是物理层的传输介质。

OSI 七层模型通信特点:对等通信 对等通信,为了使数据分组从源传送到目的地,源端 OSI 模型的每一层都必须与目标端的对等层进行通信,这种通信形式称为对等层通信。在每一层通信过程中,应用本层本人协定进行通信。

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

定时器与 requestAnimationFrame、requestIdleCallback

1. setTimeout

setTimeout 的运行机制:执行该语句时,是立刻把以后定时器代码推入事件队列,当定时器在事件列表中满足设置的工夫值时将传入的函数退出工作队列,之后的执行就交给工作队列负责。然而如果此时工作队列不为空,则需期待,所以执行定时器内代码的工夫可能会大于设置的工夫

setTimeout(() => {console.log(1);
}, 0)
console.log(2);

输入 2,1;

setTimeout的第二个参数示意在执行代码前期待的毫秒数。下面代码中,设置为 0,外表意思为 执行代码前期待的毫秒数为 0,即立刻执行。但实际上的运行后果咱们也看到了,并不是外表上看起来的样子,千万不要被坑骗了。

实际上,下面的代码并不是立刻执行的,这是因为 setTimeout 有一个最小执行工夫,HTML5 标准规定了 setTimeout() 的第二个参数的最小值(最短距离)不得低于 4 毫秒 。当指定的工夫低于该工夫时,浏览器会用最小容许的工夫作为setTimeout 的工夫距离,也就是说即便咱们把 setTimeout 的延迟时间设置为 0,实际上可能为 4 毫秒后才事件推入工作队列

定时器代码在被推送到工作队列前,会先被推入到事件列表中,当定时器在事件列表中满足设置的工夫值时会被推到工作队列,然而如果此时工作队列不为空,则需期待,所以执行定时器内代码的工夫可能会大于设置的工夫

setTimeout(() => {console.log(111);
}, 100);

下面代码示意 100ms 后执行 console.log(111),但实际上履行的工夫必定是大于 100ms 后的,100ms 只是示意 100ms 后将工作退出到 ” 工作队列 ” 中,必须等到以后代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是以后代码耗时很长,有可能要等很久,所以并没有方法保障,回调函数肯定会在setTimeout() 指定的工夫执行。

2. setTimeout 和 setInterval 区别

  • setTimeout: 指定延期后调用函数,每次 setTimeout 计时到后就会去执行,而后执行一段时间后才持续setTimeout, 两头就多了误差,(误差多少与代码的执行工夫无关)。
  • setInterval:以指定周期调用函数,而 setInterval 则是每次都准确的隔一段时间推入一个事件(然而,事件的执行工夫不肯定就不精确,还有可能是这个事件还没执行结束,下一个事件就来了).
btn.onclick = function(){setTimeout(function(){console.log(1);
    },250);
}

击该按钮后,首先将 onclick 事件处理程序退出队列。该程序执行后才设置定时器,再有 250ms 后,指定的代码才被增加到队列中期待执行。如果下面代码中的 onclick 事件处理程序执行了 300ms,那么定时器的代码至多要在定时器设置之后的300ms 后才会被执行。队列中所有的代码都要等到 javascript 过程闲暇之后能力执行,而不论它们是如何增加到队列中的。

如图所示,只管在 255ms 处增加了定时器代码,但这时候还不能执行,因为 onclick 事件处理程序仍在运行。定时器代码最早能执行的机会是在 300ms 处,即 onclick 事件处理程序完结之后。

3. setInterval 存在的一些问题:

JavaScript 中应用 setInterval 开启轮询。定时器代码可能在代码再次被增加到队列之前还没有实现执行,后果导致定时器代码间断运行好几次,而之间没有任何进展。而 javascript 引擎对这个问题的解决是:当应用 setInterval() 时,仅当没有该定时器的任何其余代码实例时,才将定时器代码增加到队列中。这确保了定时器代码退出到队列中的最小工夫距离为指定距离。

然而,这样会导致两个问题:

  • 某些距离被跳过;
  • 多个定时器的代码执行之间的距离可能比预期的小

假如,某个 onclick 事件处理程序应用 setInterval() 设置了 200ms 距离的定时器。如果事件处理程序花了 300ms 多一点工夫实现,同时定时器代码也花了差不多的工夫,就会同时呈现跳过某距离的状况

例子中的第一个定时器是在 205ms 处增加到队列中的,然而直到过了 300ms 处能力执行。当执行这个定时器代码时,在 405ms 处又给队列增加了另一个正本。在下一个距离,即 605ms 处,第一个定时器代码仍在运行,同时在队列中曾经有了一个定时器代码的实例。后果是,在这个工夫点上的定时器代码不会被增加到队列中

应用 setTimeout 结构轮询能保障每次轮询的距离。

setTimeout(function () {console.log('我被调用了');
 setTimeout(arguments.callee, 100);
}, 100);

calleearguments 对象的一个属性。它能够用于援用该函数的函数体内以后正在执行的函数。在严格模式下,第 5 版 ECMAScript (ES5) 禁止应用arguments.callee()。当一个函数必须调用本身的时候, 防止应用 arguments.callee(), 通过要么给函数表达式一个名字, 要么应用一个函数申明.

setTimeout(function fn(){console.log('我被调用了');
    setTimeout(fn, 100);
},100);

这个模式链式调用了 setTimeout(),每次函数执行的时候都会创立一个新的定时器。第二个setTimeout() 调用以后执行的函数,并为其设置另外一个定时器。这样做的益处是,在前一个定时器代码执行完之前,不会向队列插入新的定时器代码,确保不会有任何缺失的距离。而且,它能够保障在下一次定时器代码执行之前,至多要期待指定的距离,防止了间断的运行。

4. requestAnimationFrame

4.1 60fps与设施刷新率

目前大多数设施的屏幕刷新率为60 次 / 秒,如果在页面中有一个动画或者突变成果,或者用户正在滚动页面,那么浏览器渲染动画或页面的每一帧的速率也须要跟设施屏幕的刷新率保持一致。

卡顿:其中每个帧的估算工夫仅比 16 毫秒 多一点( 1 秒 / 60 = 16.6 毫秒 )。但实际上,浏览器有整顿工作要做,因而您的所有工作是须要在10 毫秒 内实现。如果无奈合乎此估算,帧率将降落,并且内容会在屏幕上抖动。此景象通常称为卡顿,会对用户体验产生负面影响。

跳帧: 如果动画切换在 16ms, 32ms, 48ms 时别离切换,跳帧就是如果到了 32ms,其余工作还未执行实现,没有去执行动画切帧,等到开始进行动画的切帧,曾经到了该执行 48ms 的切帧。就好比你玩游戏的时候卡了,过了一会,你再看画面,它不会停留你卡的中央,或者这时你的角色曾经挂掉了。必须在下一帧开始之前就曾经绘制结束;

Chrome devtool 查看实时 FPS, 关上 More tools => Rendering, 勾选 FPS meter

4.2 requestAnimationFrame实现动画

requestAnimationFrame是浏览器用于定时循环操作的一个接口,相似于 setTimeout,主要用途是按帧对网页进行重绘。

requestAnimationFrame 之前,次要借助 setTimeout/ setInterval 来编写 JS 动画,而动画的关键在于动画帧之间的工夫距离设置,这个工夫距离的设置有考究,一方面要足够小,这样动画帧之间才有连贯性,动画成果才显得平滑晦涩;另一方面要足够大,确保浏览器有足够的工夫及时实现渲染。

显示器有固定的刷新频率(60Hz 或 75Hz),也就是说,每秒最多只能重绘 60 次或 75 次,requestAnimationFrame的根本思维就是与这个刷新频率放弃同步,利用这个刷新频率进行页面重绘。此外,应用这个 API,一旦页面不处于浏览器的以后标签,就会主动进行刷新。这就节俭了 CPU、GPU 和电力。

requestAnimationFrame 是在主线程上实现。这意味着,如果主线程十分忙碌,requestAnimationFrame的动画成果会大打折扣。

requestAnimationFrame 应用一个回调函数作为参数。这个回调函数会在浏览器重绘之前调用。

requestID = window.requestAnimationFrame(callback); 

window.requestAnimFrame = (function(){
    return  window.requestAnimationFrame       || 
            window.webkitRequestAnimationFrame || 
            window.mozRequestAnimationFrame    || 
            window.oRequestAnimationFrame      || 
            window.msRequestAnimationFrame     || 
            function(callback){window.setTimeout(callback, 1000 / 60);
        };
})();

下面的代码依照 1 秒钟 60 次(大概每 16.7 毫秒一次),来模仿requestAnimationFrame

5. requestIdleCallback()

MDN 上的解释:requestIdleCallback()办法将在浏览器的闲暇时段内调用的函数排队。这使开发者可能在主事件循环上执行后盾和低优先级工作,而不会影响提早要害事件,如动画和输出响应。函数个别会按先进先调用的程序执行,然而,如果回调函数指定了执行超时工夫 timeout,则有可能为了在超时前执行函数而打乱执行程序。

requestAnimationFrame会在每次屏幕刷新的时候被调用,而 requestIdleCallback 则会在每次屏幕刷新时,判断以后帧是否还有多余的工夫,如果有,则会调用 requestAnimationFrame 的回调函数,

图片中是两个间断的执行帧,大抵能够了解为两个帧的持续时间大略为 16.67,图中黄色局部就是闲暇工夫。所以,requestIdleCallback 中的回调函数仅会在每次屏幕刷新并且有闲暇工夫时才会被调用.

利用这个个性,咱们能够在动画执行的期间,利用每帧的闲暇工夫来进行数据发送的操作,或者一些优先级比拟低的操作,此时不会使影响到动画的性能,或者和 requestAnimationFrame 搭配,能够实现一些页面性能方面的的优化,

react 的 fiber 架构也是基于 requestIdleCallback 实现的, 并且在不反对的浏览器中提供了 polyfill

总结

  • 单线程模型和工作队列 登程了解 setTimeout(fn, 0),并不是立刻执行。
  • JS 动画, 用requestAnimationFrame 会比 setInterval 成果更好
  • requestIdleCallback()罕用来切割长工作,利用闲暇工夫执行,防止主线程长时间阻塞

ES6模块与 CommonJS 模块有什么异同?

ES6 Module 和 CommonJS 模块的区别:

  • CommonJS 是对模块的浅拷⻉,ES6 Module 是对模块的引⽤,即 ES6 Module 只存只读,不能扭转其值,也就是指针指向不能变,相似 const;
  • import 的接⼝是 read-only(只读状态),不能批改其变量值。即不能批改其变量的指针指向,但能够扭转变量外部指针指向,能够对 commonJS 对从新赋值(扭转指针指向),然而对 ES6 Module 赋值会编译报错。

ES6 Module 和 CommonJS 模块的共同点:

  • CommonJS 和 ES6 Module 都能够对引⼊的对象进⾏赋值,即对对象外部属性的值进⾏扭转。

选择器权重计算形式

!important > 内联款式 = 外联款式 > ID 选择器 > 类选择器 = 伪类选择器 = 属性选择器 > 元素选择器 = 伪元素选择器 > 通配选择器 = 后辈选择器 = 兄弟选择器

  1. 属性前面加 !import 会笼罩页面内任何地位定义的元素款式
  2. 作为 style 属性写在元素内的款式
  3. id选择器
  4. 类选择器
  5. 标签选择器
  6. 通配符选择器(*
  7. 浏览器自定义或继承

同一级别:后写的会笼罩先写的

css 选择器的解析准则:选择器定位 DOM 元素是从右往左的方向,这样能够尽早的过滤掉一些不必要的款式规定和元素

类型及检测形式

1. JS 内置类型

JavaScript 的数据类型有下图所示

其中,前 7 种类型为根底类型,最初 1 种(Object)为援用类型,也是你须要重点关注的,因为它在日常工作中是应用得最频繁,也是须要关注最多技术细节的数据类型

  • JavaScript一共有 8 种数据类型,其中有 7 种根本数据类型:UndefinedNullBooleanNumberStringSymboles6新增,示意举世无双的值)和 BigIntes10 新增);
  • 1 种援用数据类型——Object(Object 实质上是由一组无序的名值对组成的)。外面蕴含 function、Array、Date等。JavaScript 不反对任何创立自定义类型的机制,而所有值最终都将是上述 8 种数据类型之一。

    • 援用数据类型: 对象Object(蕴含一般对象 -Object,数组对象 -Array,正则对象 -RegExp,日期对象 -Date,数学函数 -Math,函数对象 -Function

在这里,我想先请你重点理解上面两点,因为各种 JavaScript 的数据类型最初都会在初始化之后放在不同的内存中,因而下面的数据类型大抵能够分成两类来进行存储:

  • 原始数据类型:根底类型存储在栈内存,被援用或拷贝时,会创立一个齐全相等的变量;占据空间小、大小固定,属于被频繁应用数据,所以放入栈中存储。
  • 援用数据类型:援用类型存储在堆内存,存储的是地址,多个援用指向同一个地址,这里会波及一个“共享”的概念;占据空间大、大小不固定。援用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找援用值时,会首先检索其在栈中的地址,获得地址后从堆中取得实体。

JavaScript 中的数据是如何存储在内存中的?

在 JavaScript 中,原始类型的赋值会残缺复制变量值,而援用类型的赋值是复制援用地址。

在 JavaScript 的执行过程中,次要有三种类型内存空间,别离是 代码空间 栈空间 堆空间 。其中的代码空间次要是存储可执行代码的,原始类型(Number、String、Null、Undefined、Boolean、Symbol、BigInt) 的数据值都是间接保留在“栈”中的,援用类型 (Object) 的值是寄存在“堆”中的。因而在栈空间中(执行上下文),原始类型存储的是变量的值,而援用类型存储的是其在 ” 堆空间 ” 中的地址,当 JavaScript 须要拜访该数据的时候,是通过栈中的援用地址来拜访的,相当于多了一道转手流程。

在编译过程中,如果 JavaScript 引擎判断到一个闭包,也会在堆空间创立换一个 “closure(fn)” 的对象(这是一个外部对象,JavaScript 是无法访问的),用来保留闭包中的变量。所以闭包中的变量是存储在“堆空间”中的。

JavaScript 引擎须要用栈来维护程序执行期间上下文的状态,如果栈空间大了话,所有的数据都寄存在栈空间外面,那么会影响到上下文切换的效率,进而又影响到整个程序的执行效率。通常状况下,栈空间都不会设置太大,次要用来寄存一些原始类型的小数据。而援用类型的数据占用的空间都比拟大,所以这一类数据会被寄存到堆中,堆空间很大,能寄存很多大的数据,不过毛病是分配内存和回收内存都会占用肯定的工夫。因而须要“栈”和“堆”两种空间。

题目一:老成持重

let a = {
  name: 'lee',
  age: 18
}
let b = a;
console.log(a.name);  // 第一个 console
b.name = 'son';
console.log(a.name);  // 第二个 console
console.log(b.name);  // 第三个 console

这道题比较简单,咱们能够看到第一个 console 打进去 name 是 ‘lee’,这应该没什么疑难;然而在执行了 b.name=’son’ 之后,后果你会发现 a 和 b 的属性 name 都是 ‘son’,第二个和第三个打印后果是一样的,这里就体现了援用类型的“共享”的个性,即这两个值都存在同一块内存中共享,一个产生了扭转,另外一个也随之跟着变动。

你能够间接在 Chrome 控制台敲一遍,深刻了解一下这部分概念。上面咱们再看一段代码,它是比题目一稍简单一些的对象属性变动问题。

题目二:渐入佳境

let a = {
  name: 'Julia',
  age: 20
}
function change(o) {
  o.age = 24;
  o = {
    name: 'Kath',
    age: 30
  }
  return o;
}
let b = change(a);     // 留神这里没有 new,前面 new 相干会有专门文章解说
console.log(b.age);    // 第一个 console
console.log(a.age);    // 第二个 console

这道题波及了 function,你通过上述代码能够看到第一个 console 的后果是 30b 最初打印后果是 {name: "Kath", age: 30};第二个 console 的返回后果是 24,而 a 最初的打印后果是 {name: "Julia", age: 24}

是不是和你料想的有些区别?你要留神的是,这里的 functionreturn 带来了不一样的货色。

起因在于:函数传参进来的 o,传递的是对象在堆中的内存地址值,通过调用 o.age = 24(第 7 行代码)的确扭转了 a 对象的 age 属性;然而第 12 行代码的 return 却又把 o 变成了另一个内存地址,将 {name: "Kath", age: 30} 存入其中,最初返回 b 的值就变成了 {name: "Kath", age: 30}。而如果把第 12 行去掉,那么 b 就会返回 undefined

2. 数据类型检测

(1)typeof

typeof 对于原始类型来说,除了 null 都能够显示正确的类型

console.log(typeof 2);               // number
console.log(typeof true);            // boolean
console.log(typeof 'str');           // string
console.log(typeof []);              // object     []数组的数据类型在 typeof 中被解释为 object
console.log(typeof function(){});    // function
console.log(typeof {});              // object
console.log(typeof undefined);       // undefined
console.log(typeof null);            // object     null 的数据类型被 typeof 解释为 object

typeof 对于对象来说,除了函数都会显示 object,所以说 typeof 并不能精确判断变量到底是什么类型, 所以想判断一个对象的正确类型,这时候能够思考应用 instanceof

(2)instanceof

instanceof 能够正确的判断对象的类型,因为外部机制是通过判断对象的原型链中是不是能找到类型的 prototype

console.log(2 instanceof Number);                    // false
console.log(true instanceof Boolean);                // false 
console.log('str' instanceof String);                // false  
console.log([] instanceof Array);                    // true
console.log(function(){} instanceof Function);       // true
console.log({} instanceof Object);                   // true    
// console.log(undefined instanceof Undefined);
// console.log(null instanceof Null);
  • instanceof 能够精确地判断简单援用数据类型,然而不能正确判断根底数据类型;
  • typeof 也存在弊病,它尽管能够判断根底数据类型(null 除外),然而援用数据类型中,除了 function 类型以外,其余的也无奈判断
// 咱们也能够试着实现一下 instanceof
function _instanceof(left, right) {
    // 因为 instance 要检测的是某对象,须要有一个前置判断条件
    // 根本数据类型间接返回 false
    if(typeof left !== 'object' || left === null) return false;

    // 取得类型的原型
    let prototype = right.prototype
    // 取得对象的原型
    left = left.__proto__
    // 判断对象的类型是否等于类型的原型
    while (true) {if (left === null)
            return false
        if (prototype === left)
            return true
        left = left.__proto__
    }
}

console.log('test', _instanceof(null, Array)) // false
console.log('test', _instanceof([], Array)) // true
console.log('test', _instanceof('', Array)) // false
console.log('test', _instanceof({}, Object)) // true

(3)constructor

console.log((2).constructor === Number); // true
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true

这里有一个坑,如果我创立一个对象,更改它的原型,constructor就会变得不牢靠了

function Fn(){};

Fn.prototype=new Array();

var f=new Fn();

console.log(f.constructor===Fn);    // false
console.log(f.constructor===Array); // true 

(4)Object.prototype.toString.call()

toString()Object 的原型办法,调用该办法,能够对立返回格局为 “[object Xxx]” 的字符串,其中 Xxx 就是对象的类型。对于 Object 对象,间接调用 toString() 就能返回 [object Object];而对于其余对象,则须要通过 call 来调用,能力返回正确的类型信息。咱们来看一下代码。

Object.prototype.toString({})       // "[object Object]"
Object.prototype.toString.call({})  // 同上后果,加上 call 也 ok
Object.prototype.toString.call(1)    // "[object Number]"
Object.prototype.toString.call('1')  // "[object String]"
Object.prototype.toString.call(true)  // "[object Boolean]"
Object.prototype.toString.call(function(){})  // "[object Function]"
Object.prototype.toString.call(null)   //"[object Null]"
Object.prototype.toString.call(undefined) //"[object Undefined]"
Object.prototype.toString.call(/123/g)    //"[object RegExp]"
Object.prototype.toString.call(new Date()) //"[object Date]"
Object.prototype.toString.call([])       //"[object Array]"
Object.prototype.toString.call(document)  //"[object HTMLDocument]"
Object.prototype.toString.call(window)   //"[object Window]"

// 从下面这段代码能够看出,Object.prototype.toString.call() 能够很好地判断援用类型,甚至能够把 document 和 window 都辨别开来。

实现一个全局通用的数据类型判断办法,来加深你的了解,代码如下

function getType(obj){
  let type  = typeof obj;
  if (type !== "object") {    // 先进行 typeof 判断,如果是根底数据类型,间接返回
    return type;
  }
  // 对于 typeof 返回后果是 object 的,再进行如下的判断,正则返回后果
  return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1');  // 留神正则两头有个空格
}
/* 代码验证,须要留神大小写,哪些是 typeof 判断,哪些是 toString 判断?思考下 */
getType([])     // "Array" typeof []是 object,因而 toString 返回
getType('123')  // "string" typeof 间接返回
getType(window) // "Window" toString 返回
getType(null)   // "Null" 首字母大写,typeof null 是 object,需 toString 来判断
getType(undefined)   // "undefined" typeof 间接返回
getType()            // "undefined" typeof 间接返回
getType(function(){}) // "function" typeof 能判断,因而首字母小写
getType(/123/g)      //"RegExp" toString 返回

小结

  • typeof

    • 间接在计算机底层基于数据类型的值(二进制)进行检测
    • typeof nullobject 起因是对象存在在计算机中,都是以000 开始的二进制存储,所以检测进去的后果是对象
    • typeof 一般对象 / 数组对象 / 正则对象 / 日期对象 都是object
    • typeof NaN === 'number'
  • instanceof

    • 检测以后实例是否属于这个类的
    • 底层机制:只有以后类呈现在实例的原型上,后果都是 true
    • 不能检测根本数据类型
  • constructor

    • 反对根本类型
    • constructor 能够轻易改,也不准
  • Object.prototype.toString.call([val])

    • 返回以后实例所属类信息

判断 Target 的类型,单单用 typeof 并无奈齐全满足,这其实并不是 bug,实质起因是 JS 的万物皆对象的实践。因而要真正完满判断时,咱们须要辨别看待:

  • 根本类型(null): 应用 String(null)
  • 根本类型 (string / number / boolean / undefined) + function: – 间接应用 typeof 即可
  • 其余援用类型 (Array / Date / RegExp Error): 调用toString 后依据 [object XXX] 进行判断

3. 数据类型转换

咱们先看一段代码,理解下大抵的状况。

'123' == 123   // false or true?
''== null    // false or true?'' == 0        // false or true?
[] == 0        // false or true?
[] == ''       // false or true?
[] == ![]      // false or true?
null == undefined //  false or true?
Number(null)     // 返回什么?Number('')      // 返回什么?parseInt('');    // 返回什么?{}+10           // 返回什么?let obj = {[Symbol.toPrimitive]() {return 200;},
    valueOf() {return 300;},
    toString() {return 'Hello';}
}
console.log(obj + 200); // 这里打印进去是多少?

首先咱们要晓得,在 JS 中类型转换只有三种状况,别离是:

  • 转换为布尔值
  • 转换为数字
  • 转换为字符串

转 Boolean

在条件判断时,除了 undefinednullfalseNaN''0-0,其余所有值都转为 true,包含所有对象

Boolean(0)          //false
Boolean(null)       //false
Boolean(undefined)  //false
Boolean(NaN)        //false
Boolean(1)          //true
Boolean(13)         //true
Boolean('12')       //true

对象转原始类型

对象在转换类型的时候,会调用内置的 [[ToPrimitive]] 函数,对于该函数来说,算法逻辑一般来说如下

  • 如果曾经是原始类型了,那就不须要转换了
  • 调用 x.valueOf(),如果转换为根底类型,就返回转换的值
  • 调用 x.toString(),如果转换为根底类型,就返回转换的值
  • 如果都没有返回原始类型,就会报错

当然你也能够重写 Symbol.toPrimitive,该办法在转原始类型时调用优先级最高。

let a = {valueOf() {return 0},
  toString() {return '1'},
  [Symbol.toPrimitive]() {return 2}
}
1 + a // => 3

四则运算符

它有以下几个特点:

  • 运算中其中一方为字符串,那么就会把另一方也转换为字符串
  • 如果一方不是字符串或者数字,那么会将它转换为数字或者字符串
1 + '1' // '11'
true + true // 2
4 + [1,2,3] // "41,2,3"
  • 对于第一行代码来说,触发特点一,所以将数字 1 转换为字符串,失去后果 '11'
  • 对于第二行代码来说,触发特点二,所以将 true 转为数字 1
  • 对于第三行代码来说,触发特点二,所以将数组通过 toString转为字符串 1,2,3,失去后果 41,2,3

另外对于加法还须要留神这个表达式 'a' + + 'b'

'a' + + 'b' // -> "aNaN"
  • 因为 + 'b' 等于 NaN,所以后果为 "aNaN",你可能也会在一些代码中看到过 + '1'的模式来疾速获取 number 类型。
  • 那么对于除了加法的运算符来说,只有其中一方是数字,那么另一方就会被转为数字
4 * '3' // 12
4 * [] // 0
4 * [1, 2] // NaN

比拟运算符

  • 如果是对象,就通过 toPrimitive 转换对象
  • 如果是字符串,就通过 unicode 字符索引来比拟
let a = {valueOf() {return 0},
  toString() {return '1'}
}
a > -1 // true

在以上代码中,因为 a 是对象,所以会通过 valueOf 转换为原始类型再比拟值。

强制类型转换

强制类型转换形式包含 Number()parseInt()parseFloat()toString()String()Boolean(),这几种办法都比拟相似

  • Number() 办法的强制转换规则
  • 如果是布尔值,truefalse 别离被转换为 10
  • 如果是数字,返回本身;
  • 如果是 null,返回 0
  • 如果是 undefined,返回 NaN
  • 如果是字符串,遵循以下规定:如果字符串中只蕴含数字(或者是 0X / 0x 结尾的十六进制数字字符串,容许蕴含正负号),则将其转换为十进制;如果字符串中蕴含无效的浮点格局,将其转换为浮点数值;如果是空字符串,将其转换为 0;如果不是以上格局的字符串,均返回 NaN;
  • 如果是 Symbol,抛出谬误;
  • 如果是对象,并且部署了 [Symbol.toPrimitive],那么调用此办法,否则调用对象的 valueOf() 办法,而后根据后面的规定转换返回的值;如果转换的后果是 NaN,则调用对象的 toString() 办法,再次按照后面的程序转换返回对应的值。
Number(true);        // 1
Number(false);       // 0
Number('0111');      //111
Number(null);        //0
Number('');          //0
Number('1a');        //NaN
Number(-0X11);       //-17
Number('0X11')       //17

Object 的转换规则

对象转换的规定,会先调用内置的 [ToPrimitive] 函数,其规定逻辑如下:

  • 如果部署了 Symbol.toPrimitive 办法,优先调用再返回;
  • 调用 valueOf(),如果转换为根底类型,则返回;
  • 调用 toString(),如果转换为根底类型,则返回;
  • 如果都没有返回根底类型,会报错。
var obj = {
  value: 1,
  valueOf() {return 2;},
  toString() {return '3'},
  [Symbol.toPrimitive]() {return 4}
}
console.log(obj + 1); // 输入 5
// 因为有 Symbol.toPrimitive,就优先执行这个;如果 Symbol.toPrimitive 这段代码删掉,则执行 valueOf 打印后果为 3;如果 valueOf 也去掉,则调用 toString 返回 '31'(字符串拼接)
// 再看两个非凡的 case:10 + {}
// "10[object Object]",留神:{}会默认调用 valueOf 是{},不是根底类型持续转换,调用 toString,返回后果 "[object Object]",于是和 10 进行 '+' 运算,依照字符串拼接规定来,参考 '+' 的规定 C
[1,2,undefined,4,5] + 10
// "1,2,,4,510",留神 [1,2,undefined,4,5] 会默认先调用 valueOf 后果还是这个数组,不是根底数据类型持续转换,也还是调用 toString,返回 "1,2,,4,5",而后再和 10 进行运算,还是依照字符串拼接规定,参考 '+' 的第 3 条规定

‘==’ 的隐式类型转换规定

  • 如果类型雷同,毋庸进行类型转换;
  • 如果其中一个操作值是 null 或者 undefined,那么另一个操作符必须为 null 或者 undefined,才会返回 true,否则都返回 false
  • 如果其中一个是 Symbol 类型,那么返回 false
  • 两个操作值如果为 string 和 number 类型,那么就会将字符串转换为 number
  • 如果一个操作值是 boolean,那么转换成 number
  • 如果一个操作值为 object 且另一方为 stringnumber 或者 symbol,就会把 object 转为原始类型再进行判断(调用 objectvalueOf/toString 办法进行转换)。
null == undefined       // true  规定 2
null == 0               // false 规定 2
''== null              // false 规定 2'' == 0                 // true  规定 4 字符串转隐式转换成 Number 之后再比照
'123' == 123            // true  规定 4 字符串转隐式转换成 Number 之后再比照
0 == false              // true  e 规定 布尔型隐式转换成 Number 之后再比照
1 == true               // true  e 规定 布尔型隐式转换成 Number 之后再比照
var a = {
  value: 0,
  valueOf: function() {
    this.value++;
    return this.value;
  }
};
// 留神这里 a 又能够等于 1、2、3
console.log(a == 1 && a == 2 && a ==3);  //true f 规定 Object 隐式转换
// 注:然而执行过 3 遍之后,再从新执行 a == 3 或之前的数字就是 false,因为 value 曾经加上去了,这里须要留神一下

‘+’ 的隐式类型转换规定

‘+’ 号操作符,不仅能够用作数字相加,还能够用作字符串拼接。仅当 ‘+’ 号两边都是数字时,进行的是加法运算;如果两边都是字符串,则间接拼接,毋庸进行隐式类型转换。

  • 如果其中有一个是字符串,另外一个是 undefinednull 或布尔型,则调用 toString() 办法进行字符串拼接;如果是纯对象、数组、正则等,则默认调用对象的转换方法会存在优先级,而后再进行拼接。
  • 如果其中有一个是数字,另外一个是 undefinednull、布尔型或数字,则会将其转换成数字进行加法运算,对象的状况还是参考上一条规定。
  • 如果其中一个是字符串、一个是数字,则依照字符串规定进行拼接
1 + 2        // 3  惯例状况
'1' + '2'    // '12' 惯例状况
// 上面看一下非凡状况
'1' + undefined   // "1undefined" 规定 1,undefined 转换字符串
'1' + null        // "1null" 规定 1,null 转换字符串
'1' + true        // "1true" 规定 1,true 转换字符串
'1' + 1n          // '11' 比拟非凡字符串和 BigInt 相加,BigInt 转换为字符串
1 + undefined     // NaN  规定 2,undefined 转换数字相加 NaN
1 + null          // 1    规定 2,null 转换为 0
1 + true          // 2    规定 2,true 转换为 1,二者相加为 2
1 + 1n            // 谬误  不能把 BigInt 和 Number 类型间接混合相加
'1' + 3           // '13' 规定 3,字符串拼接

整体来看,如果数据中有字符串,JavaScript 类型转换还是更偏向于转换成字符串,因为第三条规定中能够看到,在字符串和数字相加的过程中最初返回的还是字符串,这里须要关注一下

null 和 undefined 的区别?

  • 首先 UndefinedNull 都是根本数据类型,这两个根本数据类型别离都只有一个值,就是 undefinednull
  • undefined 代表的含意是未定义,null 代表的含意是空对象(其实不是真的对象,请看上面的留神!)。个别变量申明了但还没有定义的时候会返回 undefinednull 次要用于赋值给一些可能会返回对象的变量,作为初始化。

其实 null 不是对象,尽管 typeof null 会输入 object,然而这只是 JS 存在的一个悠久 Bug。在 JS 的最后版本中应用的是 32 位零碎,为了性能思考应用低位存储变量的类型信息,000 结尾代表是对象,然而 null 示意为全零,所以将它谬误的判断为 object。尽管当初的外部类型判断代码曾经扭转了,然而对于这个 Bug 却是始终流传下来。

  • undefined 在 js 中不是一个保留字,这意味着咱们能够应用 undefined 来作为一个变量名,这样的做法是十分危险的,它会影响咱们对 undefined 值的判断。然而咱们能够通过一些办法取得平安的 undefined 值,比如说 void 0
  • 当咱们对两种类型应用 typeof 进行判断的时候,Null 类型化会返回“object”,这是一个历史遗留的问题。当咱们应用双等号对两种类型的值进行比拟时会返回 true,应用三个等号时会返回 false。

TCP/IP 五层协定

TCP/IP五层协定和 OSI 的七层协定对应关系如下:

  • 应用层 (application layer):间接为利用过程提供服务。应用层协定定义的是利用过程间通信和交互的规定,不同的利用有着不同的应用层协定,如 HTTP 协定(万维网服务)、FTP 协定(文件传输)、SMTP 协定(电子邮件)、DNS(域名查问)等。
  • 传输层 (transport layer):有时也译为运输层,它负责为两台主机中的过程提供通信服务。该层次要有以下两种协定:

    • 传输控制协议 (Transmission Control Protocol,TCP):提供面向连贯的、牢靠的数据传输服务,数据传输的根本单位是报文段(segment);
    • 用户数据报协定 (User Datagram Protocol,UDP):提供无连贯的、尽最大致力的数据传输服务,但不保障数据传输的可靠性,数据传输的根本单位是用户数据报。
  • 网络层 (internet layer):有时也译为网际层,它负责为两台主机提供通信服务,并通过抉择适合的路由将数据传递到指标主机。
  • 数据链路层 (data link layer):负责将网络层交下来的 IP 数据报封装成帧,并在链路的两个相邻节点间传送帧,每一帧都蕴含数据和必要的管制信息(如同步信息、地址信息、差错控制等)。
  • 物理层 (physical Layer):确保数据能够在各种物理媒介上进行传输,为数据的传输提供牢靠的环境。

从上图中能够看出,TCP/IP模型比 OSI 模型更加简洁,它把 应用层 / 表示层 / 会话层 全副整合为了 应用层

在每一层都工作着不同的设施,比方咱们罕用的交换机就工作在数据链路层的,个别的路由器是工作在网络层的。在每一层实现的协定也各不同,即每一层的服务也不同,下图列出了每层次要的传输协定:

同样,TCP/IP五层协定的通信形式也是对等通信:

TCP 和 UDP 的应用场景

  • TCP 利用场景: 效率要求绝对低,但对准确性要求绝对高的场景。因为传输中须要对数据确认、重发、排序等操作,相比之下效率没有 UDP 高。例如:文件传输(精确高要求高、然而速度能够绝对慢)、承受邮件、近程登录。
  • UDP 利用场景: 效率要求绝对高,对准确性要求绝对低的场景。例如:QQ 聊天、在线视频、网络语音电话(即时通讯,速度要求高,然而呈现偶然断续不是太大问题,并且此处齐全不能够应用重发机制)、播送通信(播送、多播)。

左右两边定宽,两头自适应

float,float + calc, 圣杯布局(设置 BFC,margin 负值法),flex

.wrap {
  width: 100%;
  height: 200px;
}
.wrap > div {height: 100%;}
/* 计划 1 */
.left {
  width: 120px;
  float: left;
}
.right {
  float: right;
  width: 120px;
}
.center {margin: 0 120px;}
/* 计划 2 */
.left {
  width: 120px;
  float: left;
}
.right {
  float: right;
  width: 120px;
}
.center {width: calc(100% - 240px);
  margin-left: 120px;
}
/* 计划 3 */
.wrap {display: flex;}
.left {width: 120px;}
.right {width: 120px;}
.center {flex: 1;}

intanceof 操作符的实现原理及实现

instanceof 运算符用于判断构造函数的 prototype 属性是否呈现在对象的原型链中的任何地位。

function myInstanceof(left, right) {
  // 获取对象的原型
  let proto = Object.getPrototypeOf(left)
  // 获取构造函数的 prototype 对象
  let prototype = right.prototype; 

  // 判断构造函数的 prototype 对象是否在对象的原型链上
  while (true) {if (!proto) return false;
    if (proto === prototype) return true;
    // 如果没有找到,就持续从其原型上找,Object.getPrototypeOf 办法用来获取指定对象的原型
    proto = Object.getPrototypeOf(proto);
  }
}

UDP 协定为什么不牢靠?

UDP 在传输数据之前不须要先建设连贯,远地主机的运输层在接管到 UDP 报文后,不须要确认,提供不牢靠交付。总结就以下四点:

  • 不保障音讯交付:不确认,不重传,无超时
  • 不保障交付程序:不设置包序号,不重排,不会产生队首阻塞
  • 不跟踪连贯状态:不用建设连贯或重启状态机
  • 不进行拥塞管制:不内置客户端或网络反馈机制

说一下 HTML5 drag API

  • dragstart:事件主体是被拖放元素,在开始拖放被拖放元素时触发。
  • darg:事件主体是被拖放元素,在正在拖放被拖放元素时触发。
  • dragenter:事件主体是指标元素,在被拖放元素进入某元素时触发。
  • dragover:事件主体是指标元素,在被拖放在某元素内挪动时触发。
  • dragleave:事件主体是指标元素,在被拖放元素移出指标元素是触发。
  • drop:事件主体是指标元素,在指标元素齐全承受被拖放元素时触发。
  • dragend:事件主体是被拖放元素,在整个拖放操作完结时触发。

实现一个三角形

CSS 绘制三角形次要用到的是 border 属性,也就是边框。

平时在给盒子设置边框时,往往都设置很窄,就可能误以为边框是由矩形组成的。实际上,border 属性是右三角形组成的,上面看一个例子:

div {
    width: 0;
    height: 0;
    border: 100px solid;
    border-color: orange blue red green;
}

将元素的长宽都设置为 0

(1)三角 1

div {width: 0;    height: 0;    border-top: 50px solid red;    border-right: 50px solid transparent;    border-left: 50px solid transparent;}

(2)三角 2

div {
    width: 0;
    height: 0;
    border-bottom: 50px solid red;
    border-right: 50px solid transparent;
    border-left: 50px solid transparent;
}

(3)三角 3

div {
    width: 0;
    height: 0;
    border-left: 50px solid red;
    border-top: 50px solid transparent;
    border-bottom: 50px solid transparent;
}

(4)三角 4

div {
    width: 0;
    height: 0;
    border-right: 50px solid red;
    border-top: 50px solid transparent;
    border-bottom: 50px solid transparent;
}

(5)三角 5

div {
    width: 0;
    height: 0;
    border-top: 100px solid red;
    border-right: 100px solid transparent;
}

还有很多,就不一一实现了,总体的准则就是通过上下左右边框来管制三角形的方向,用边框的宽度比来管制三角形的角度。

对 this 对象的了解

this 是执行上下文中的一个属性,它指向最初一次调用这个办法的对象。在理论开发中,this 的指向能够通过四种调用模式来判断。

  • 第一种是 函数调用模式,当一个函数不是一个对象的属性时,间接作为函数来调用时,this 指向全局对象。
  • 第二种是 办法调用模式,如果一个函数作为一个对象的办法来调用时,this 指向这个对象。
  • 第三种是 结构器调用模式,如果一个函数用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象。
  • 第四种是 apply、call 和 bind 调用模式,这三个办法都能够显示的指定调用函数的 this 指向。其中 apply 办法接管两个参数:一个是 this 绑定的对象,一个是参数数组。call 办法接管的参数,第一个是 this 绑定的对象,前面的其余参数是传入函数执行的参数。也就是说,在应用 call() 办法时,传递给函数的参数必须一一列举进去。bind 办法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this 指向除了应用 new 时会被扭转,其余状况下都不会扭转。

这四种形式,应用结构器调用模式的优先级最高,而后是 apply、call 和 bind 调用模式,而后是办法调用模式,而后是函数调用模式。

new 操作符的实现原理

new 操作符的执行过程:

(1)首先创立了一个新的空对象

(2)设置原型,将对象的原型设置为函数的 prototype 对象。

(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象增加属性)

(4)判断函数的返回值类型,如果是值类型,返回创立的对象。如果是援用类型,就返回这个援用类型的对象。

具体实现:

function objectFactory() {
  let newObject = null;
  let constructor = Array.prototype.shift.call(arguments);
  let result = null;
  // 判断参数是否是一个函数
  if (typeof constructor !== "function") {console.error("type error");
    return;
  }
  // 新建一个空对象,对象的原型为构造函数的 prototype 对象
  newObject = Object.create(constructor.prototype);
  // 将 this 指向新建对象,并执行函数
  result = constructor.apply(newObject, arguments);
  // 判断返回对象
  let flag = result && (typeof result === "object" || typeof result === "function");
  // 判断返回后果
  return flag ? result : newObject;
}
// 应用办法
objectFactory(构造函数, 初始化参数);

箭头函数的 this 指向哪⾥?

箭头函数不同于传统 JavaScript 中的函数,箭头函数并没有属于⾃⼰的 this,它所谓的 this 是捕捉其所在高低⽂的 this 值,作为⾃⼰的 this 值,并且因为没有属于⾃⼰的 this,所以是不会被 new 调⽤的,这个所谓的 this 也不会被扭转。

能够⽤ Babel 了解⼀下箭头函数:

// ES6 
const obj = {getArrow() {return () => {console.log(this === obj); 
    }; 
  } 
}

转化后:

// ES5,由 Babel 转译
var obj = {getArrow: function getArrow() { 
     var _this = this; 
     return function () {console.log(_this === obj); 
     }; 
   } 
};
正文完
 0