(如下全部答案仅供参考)javascript
参考答案
php
防抖css
触发高频事件后n秒内函数只会执行一次,若是n秒内高频事件再次被触发,则从新计算时间
每次触发事件时都取消以前的延时调用方法
function debounce(fn) { let timeout = null; // 建立一个标记用来存放定时器的返回值 return function () { clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉 timeout = setTimeout(() => { // 而后又建立一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内若是还有字符输入的话,就不会执行 fn 函数 fn.apply(this, arguments); }, 500); }; } function sayHi() { console.log('防抖成功'); } var inp = document.getElementById('inp'); inp.addEventListener('input', debounce(sayHi)); // 防抖
节流html
高频事件触发,但在n秒内只会执行一次,因此节流会稀释函数的执行频率
每次触发事件时都判断当前是否有等待执行的延时函数
function throttle(fn) { let canRun = true; // 经过闭包保存一个标记 return function () { if (!canRun) return; // 在函数开头判断标记是否为true,不为true则return canRun = false; // 当即设置为false setTimeout(() => { // 将外部传入的函数的执行放在setTimeout中 fn.apply(this, arguments); // 最后在setTimeout执行完毕后再把标记设置为true(关键)表示能够执行下一次循环了。当定时器没有执行的时候标记永远是false,在开头被return掉 canRun = true; }, 500); }; } function sayHi(e) { console.log(e.target.innerWidth, e.target.innerHeight); } window.addEventListener('resize', throttle(sayHi));
误区:咱们常常说get请求参数的大小存在限制,而post请求的参数大小是无限制的。前端
参考答案
vue
实际上HTTP 协议从未规定 GET/POST 的请求长度限制是多少。对get请求参数的限制是来源与浏览器或web服务器,浏览器或web服务器限制了url的长度。为了明确这个概念,咱们必须再次强调下面几点:java
补充补充一个get和post在缓存方面的区别:node
可从IIFE、AMD、CMD、CommonJS、UMD、webpack(require.ensure)、ES Module、<script type="module">
这几个角度考虑。react
参考答案
webpack
模块化主要是用来抽离公共代码,隔离做用域,避免变量冲突等。
IIFE: 使用自执行函数来编写模块化,特色:在一个单独的函数做用域中执行代码,避免变量冲突。
(function(){ return { data:[] } })()
AMD: 使用requireJS 来编写模块化,特色:依赖必须提早声明好。
define('./index.js',function(code){ // code 就是index.js 返回的内容 })
CMD: 使用seaJS 来编写模块化,特色:支持动态引入依赖文件。
define(function(require, exports, module) { var indexCode = require('./index.js'); })
CommonJS: nodejs 中自带的模块化。
var fs = require('fs');
UMD:兼容AMD,CommonJS 模块化语法。
webpack(require.ensure):webpack 2.x 版本中的代码分割。
ES Modules: ES6 引入的模块化,支持import 来引入另外一个 js 。
import a from 'a';
参考答案
npm install
命令查询node_modules目录之中是否已经存在指定模块
若不存在
.npm
目录里node_modules
目录输入 npm install 命令并敲下回车后,会经历以下几个阶段(以 npm 5.5.1 为例):
当前 npm 工程若是定义了 preinstall 钩子此时会被执行。
首先须要作的是肯定工程中的首层依赖,也就是 dependencies 和 devDependencies 属性中直接指定的模块(假设此时没有添加 npm install 参数)。
工程自己是整棵依赖树的根节点,每一个首层依赖模块都是根节点下面的一棵子树,npm 会开启多进程从每一个首层依赖模块开始逐步寻找更深层级的节点。
获取模块
获取模块是一个递归的过程,分为如下几步:
上一步获取到的是一棵完整的依赖树,其中可能包含大量重复模块。好比 A 模块依赖于 loadsh,B 模块一样依赖于 lodash。在 npm3 之前会严格按照依赖树的结构进行安装,所以会形成模块冗余。
从 npm3 开始默认加入了一个 dedupe 的过程。它会遍历全部节点,逐个将模块放在根节点下面,也就是 node-modules 的第一层。当发现有重复模块时,则将其丢弃。
这里须要对重复模块进行一个定义,它指的是模块名相同且 semver 兼容。每一个 semver 都对应一段版本容许范围,若是两个模块的版本容许范围存在交集,那么就能够获得一个兼容版本,而没必要版本号彻底一致,这可使更多冗余模块在 dedupe 过程当中被去掉。
好比 node-modules 下 foo 模块依赖 lodash@^1.0.0,bar 模块依赖 lodash@^1.1.0,则 ^1.1.0 为兼容版本。
而当 foo 依赖 lodash@^2.0.0,bar 依赖 lodash@^1.1.0,则依据 semver 的规则,两者不存在兼容版本。会将一个版本放在 node_modules 中,另外一个仍保留在依赖树里。
举个例子,假设一个依赖树本来是这样:
node_modules
-- foo
---- lodash@version1
-- bar
---- lodash@version2
假设 version1 和 version2 是兼容版本,则通过 dedupe 会成为下面的形式:
node_modules
-- foo
-- bar
-- lodash(保留的版本为兼容版本)
假设 version1 和 version2 为非兼容版本,则后面的版本保留在依赖树中:
node_modules
-- foo
-- lodash@version1
-- bar
---- lodash@version2
这一步将会更新工程中的 node_modules,并执行模块中的生命周期函数(按照 preinstall、install、postinstall 的顺序)。
当前 npm 工程若是定义了钩子此时会被执行(按照 install、postinstall、prepublish、prepare 的顺序)。
最后一步是生成或更新版本描述文件,npm install 过程完成。
参考答案
ES5的继承时经过prototype或构造函数机制来实现。ES5的继承实质上是先建立子类的实例对象,而后再将父类的方法添加到this上(Parent.apply(this))。
ES6的继承机制彻底不一样,实质上是先建立父类的实例对象this(因此必须先调用父类的super()方法),而后再用子类的构造函数修改this。
具体的:ES6经过class关键字定义类,里面有构造方法,类之间经过extends关键字实现继承。子类必须在constructor方法中调用super方法,不然新建实例报错。由于子类没有本身的this对象,而是继承了父类的this对象,而后对其进行加工。若是不调用super方法,子类得不到this对象。
ps:super关键字指代父类的实例,即父类的this对象。在子类构造函数中,调用super后,才可以使用this关键字,不然报错。
参考答案
由于js是单线程的,浏览器遇到setTimeout或者setInterval会先执行完当前的代码块,在此以前会把定时器推入浏览器的待执行事件队列里面,等到浏览器执行完当前代码以后会看一下事件队列里面有没有任务,有的话才执行定时器的代码。因此即便把定时器的时间设置为0仍是会先执行当前的一些代码。
function test(){ var aa = 0; var testSet = setInterval(function(){ aa++; console.log(123); if(aa<10){ clearInterval(testSet); } },20); var testSet1 = setTimeout(function(){ console.log(321) },1000); for(var i=0;i<10;i++){ console.log('test'); } } test()
输出结果:
test //10次 undefined 123 321
参考答案
输出:[1, NaN, NaN]
var new_array = arr.map(function callback(currentValue[, index[, array]]) { // Return element for new_array }[, thisArg])
这个callback一共能够接收三个参数,其中第一个参数表明当前被处理的元素,而第二个参数表明该元素的索引。
parseInt(string, radix)
参考答案
Doctype声明于文档最前面,告诉浏览器以何种方式来渲染页面,这里有两种模式,严格模式和混杂模式。
参考答案
fetch发送post请求的时候,老是发送2次,第一次状态码是204,第二次才成功?
缘由很简单,由于你用fetch的post请求的时候,致使fetch 第一次发送了一个Options请求,询问服务器是否支持修改的请求头,若是服务器支持,则在第二次中发送真正的请求。
参考答案
http协议是超文本传输协议,位于tcp/ip四层模型中的应用层;经过请求/响应的方式在客户端和服务器之间进行通讯;可是缺乏安全性,http协议信息传输是经过明文的方式传输,不作任何加密,至关于在网络上裸奔;容易被中间人恶意篡改,这种行为叫作中间人攻击;
为了安全性,双方可使用对称加密的方式key进行信息交流,可是这种方式对称加密秘钥也会被拦截,也不够安全,进而仍是存在被中间人攻击风险;
因而人们又想出来另一种方式,使用非对称加密的方式;使用公钥/私钥加解密;通讯方A发起通讯并携带本身的公钥,接收方B经过公钥来加密对称秘钥;而后发送给发起方A;A经过私钥解密;双发接下来经过对称秘钥来进行加密通讯;可是这种方式仍是会存在一种安全性;中间人虽然不知道发起方A的私钥,可是能够作到偷天换日,将拦截发起方的公钥key;并将本身生成的一对公/私钥的公钥发送给B;接收方B并不知道公钥已经被偷偷换过;按照以前的流程,B经过公钥加密本身生成的对称加密秘钥key2;发送给A;
此次通讯再次被中间人拦截,尽管后面的通讯,二者仍是用key2通讯,可是中间人已经掌握了Key2;能够进行轻松的加解密;仍是存在被中间人攻击风险;
解决困境:权威的证书颁发机构CA来解决;
说明:各大浏览器和操做系统已经维护了全部的权威证书机构的名称和公钥。B只须要知道是哪一个权威机构发的证书,使用对应的机构公钥,就能够解密出证书签名;接下来,B使用一样的规则,生成本身的证书签名,若是两个签名是一致的,说明证书是有效的;
签名验证成功后,B就能够再次利用机构的公钥,解密出A的公钥key1;接下来的操做,就是和以前同样的流程了;
由于证书的签名是由服务器端网址等信息生成的,而且经过第三方机构的私钥加密中间人没法篡改; 因此最关键的问题是证书签名的真伪;
参考答案
三次握手之因此是三次是保证client和server均让对方知道本身的接收和发送能力没问题而保证的最小次数。
第一次client => server 只能server判断出client具有发送能力
第二次 server => client client就能够判断出server具有发送和接受能力。此时client还需让server知道本身接收能力没问题因而就有了第三次
第三次 client => server 双方均保证了本身的接收和发送能力没有问题
其中,为了保证后续的握手是为了应答上一个握手,每次握手都会带一个标识 seq,后续的ACK都会对这个seq进行加一来进行确认。
参考答案
优势:跨域完毕以后DOM操做和互相之间的JavaScript调用都是没有问题的
缺点:1.若结果要以URL参数传递,这就意味着在结果数据量很大的时候须要分割传递,巨烦。2.还有一个是iframe自己带来的,母页面和iframe自己的交互自己就有安全性限制。
优势:能够直接返回json格式的数据,方便处理
缺点:只接受GET请求方式
优势:能够访问任何url,通常用来进行点击追踪,作页面分析经常使用的方法
缺点:不能访问响应文本,只能监听是否响应
参考答案
http传输的数据都是未加密的,也就是明文的,网景公司设置了SSL协议来对http协议传输的数据进行加密处理,简单来讲https协议是由http和ssl协议构建的可进行加密传输和身份认证的网络协议,比http协议的安全性更高。 主要的区别以下:
参考答案
Bom是浏览器对象
location对象
history对象
Navigator对象
参考答案
共同点:都是保存在浏览器端,而且是同源的
补充说明一下cookie的做用:
参考答案
XSS(跨站脚本攻击)是指攻击者在返回的HTML中嵌入javascript脚本,为了减轻这些攻击,须要在HTTP头部配上,set-cookie:
结果应该是这样的:Set-Cookie=.....
参考答案
其中一个主要的区别在于浏览器的event loop 和nodejs的event loop 在处理异步事件的顺序是不一样的,nodejs中有micro event;其中Promise属于micro event 该异步事件的处理顺序就和浏览器不一样.nodejs V11.0以上 这二者之间的顺序就相同了.
function test () { console.log('start') setTimeout(() => { console.log('children2') Promise.resolve().then(() => {console.log('children2-1')}) }, 0) setTimeout(() => { console.log('children3') Promise.resolve().then(() => {console.log('children3-1')}) }, 0) Promise.resolve().then(() => {console.log('children1')}) console.log('end') } test() // 以上代码在node11如下版本的执行结果(先执行全部的宏任务,再执行微任务) // start // end // children1 // children2 // children3 // children2-1 // children3-1 // 以上代码在node11及浏览器的执行结果(顺序执行宏任务和微任务) // start // end // children1 // children2 // children2-1 // children3 // children3-1
参考答案
https协议由 http + ssl 协议构成,具体的连接过程可参考SSL或TLS握手的概述
中间人攻击过程以下:
防范方法:
参考答案
(1). 减小HTTP请求数
这条策略基本上全部前端人都知道,并且也是最重要最有效的。都说要减小HTTP请求,那请求多了到底会怎么样呢?首先,每一个请求都是有成本的,既包 含时间成本也包含资源成本。一个完整的请求都须要通过DNS寻址、与服务器创建链接、发送数据、等待服务器响应、接收数据这样一个“漫长”而复杂的过程。 时间成本就是用户须要看到或者“感觉”到这个资源是必需要等待这个过程结束的,资源上因为每一个请求都须要携带数据,所以每一个请求都须要占用带宽。
另外,因为浏览器进行并发请求的请求数是有上限的,所以请求数多了之后,浏览器须要分批进行请求,所以会增长用户的等待时间,会给 用户形成站点速度慢这样一个印象,即便可能用户能看到的第一屏的资源都已经请求完了,可是浏览器的进度条会一直存在。减小HTTP请求数的主要途径包括:
(2). 从设计实现层面简化页面
若是你的页面像百度首页同样简单,那么接下来的规则基本上都用不着了。保持页面简洁、减小资源的使用时最直接的。若是不是这样,你的页面须要华丽的皮肤,则继续阅读下面的内容。
(3). 合理设置HTTP缓存
缓存的力量是强大的,恰当的缓存设置能够大大的减小HTTP请求。以有啊首页为例,当浏览器没有缓存的时候访问一共会发出78个请求,共600多K 数据(如图1.1),而当第二次访问即浏览器已缓存以后访问则仅有10个请求,共20多K数据(如图1.2)。(这里须要说明的是,若是直接F5刷新页面 的话效果是不同的,这种状况下请求数仍是同样,不过被缓存资源的请求服务器是304响应,只有Header没有Body,能够节省带宽)
怎样才算合理设置?原则很简单,能缓存越多越好,能缓存越久越好。例如,不多变化的图片资源能够直接经过HTTP Header中的Expires设置一个很长的过时头;变化不频繁而又可能会变的资源可使用Last-Modifed来作请求验证。尽量的让资源可以 在缓存中待得更久。
(4). 资源合并与压缩
若是能够的话,尽量的将外部的脚本、样式进行合并,多个合为一个。另外,CSS、Javascript、Image均可以用相应的工具进行压缩,压缩后每每能省下很多空间。
(5). CSS Sprites
合并CSS图片,减小请求数的又一个好办法。
(6). Inline Images
使用data: URL scheme的方式将图片嵌入到页面或CSS中,若是不考虑资源管理上的问题的话,不失为一个好办法。若是是嵌入页面的话换来的是增大了页面的体积,并且没法利用浏览器缓存。使用在CSS中的图片则更为理想一些。
(7). Lazy Load Images
这条策略实际上并不必定能减小HTTP请求数,可是却能在某些条件下或者页面刚加载时减小HTTP请求数。对于图片而言,在页面刚加载的时候能够只 加载第一屏,当用户继续日后滚屏的时候才加载后续的图片。这样一来,假如用户只对第一屏的内容感兴趣时,那剩余的图片请求就都节省了。有啊首页曾经的作法 是在加载的时候把第一屏以后的图片地址缓存在Textarea标签中,待用户往下滚屏的时候才“惰性”加载。
参考答案
重绘(Repaint)和回流(Reflow)
重绘和回流是渲染步骤中的一小节,可是这两个步骤对于性能影响很大。
color
就叫称为重绘回流一定会发生重绘,重绘不必定会引起回流。回流所需的成本比重绘高的多,改变深层次的节点极可能致使父节点的一系列回流。
因此如下几个动做可能会致使性能问题:
不少人不知道的是,重绘和回流其实和 Event loop 有关。
resize
或者 scroll
,有的话会去触发事件,因此 resize
和 scroll
事件也是至少 16ms 才会触发一次,而且自带节流功能。requestAnimationFrame
回调IntersectionObserver
回调,该方法用于判断元素是否可见,能够用于懒加载上,可是兼容性很差requestIdleCallback
回调。减小重绘和回流
使用 translate
替代 top
<div class="test"></div> <style> .test { position: absolute; top: 10px; width: 100px; height: 100px; background: red; } </style> <script> setTimeout(() => { // 引发回流 document.querySelector('.test').style.top = '100px' }, 1000) </script>
使用 visibility
替换 display: none
,由于前者只会引发重绘,后者会引起回流(改变了布局)
把 DOM 离线后修改,好比:先把 DOM 给 display:none
(有一次 Reflow),而后你修改100次,而后再把它显示出来
不要把 DOM 结点的属性值放在一个循环里当成循环里的变量
for(let i = 0; i < 1000; i++) { // 获取 offsetTop 会致使回流,由于须要去获取正确的值 console.log(document.querySelector('.test').style.offsetTop) }
requestAnimationFrame
video
标签,浏览器会自动将该节点变为图层。参考答案
vue和react都是采用diff算法来对比新旧虚拟节点,从而更新节点。在vue的diff函数中(建议先了解一下diff算法过程)。
在交叉对比中,当新节点跟旧节点头尾交叉对比
没有结果时,会根据新节点的key去对比旧节点数组中的key,从而找到相应旧节点(这里对应的是一个key => index 的map映射)。若是没找到就认为是一个新增节点。而若是没有key,那么就会采用遍历查找的方式去找到对应的旧节点。一种一个map映射,另外一种是遍历查找。相比而言。map映射的速度更快。
vue部分源码以下:
// vue项目 src/core/vdom/patch.js -488行 // 如下是为了阅读性进行格式化后的代码 // oldCh 是一个旧虚拟节点数组 if (isUndef(oldKeyToIdx)) { oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) } if(isDef(newStartVnode.key)) { // map 方式获取 idxInOld = oldKeyToIdx[newStartVnode.key] } else { // 遍历方式获取 idxInOld = findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx) }
建立map函数
function createKeyToOldIdx (children, beginIdx, endIdx) { let i, key const map = {} for (i = beginIdx; i <= endIdx; ++i) { key = children[i].key if (isDef(key)) map[key] = i } return map }
遍历寻找
// sameVnode 是对比新旧节点是否相同的函数 function findIdxInOld (node, oldCh, start, end) { for (let i = start; i < end; i++) { const c = oldCh[i] if (isDef(c) && sameVnode(node, c)) return i } }
参考答案
在React中,若是是由React引起的事件处理(好比经过onClick引起的事件处理),调用setState不会同步更新this.state,除此以外的setState调用会同步执行this.state。所谓“除此以外”,指的是绕过React经过addEventListener直接添加的事件处理函数,还有经过setTimeout/setInterval产生的异步调用。
缘由:在React的setState函数实现中,会根据一个变量isBatchingUpdates判断是直接更新this.state仍是放到队列中回头再说,而isBatchingUpdates默认是false,也就表示setState会同步更新this.state,可是,有一个函数batchedUpdates,这个函数会把isBatchingUpdates修改成true,而当React在调用事件处理函数以前就会调用这个batchedUpdates,形成的后果,就是由React控制的事件处理过程setState不会同步更新this.state。
class Example extends React.Component { constructor() { super(); this.state = { val: 0 }; } componentDidMount() { this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 1 次 log this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 2 次 log setTimeout(() => { this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 3 次 log this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 4 次 log }, 0); } render() { return null; } };
一、第一次和第二次都是在 react 自身生命周期内,触发时 isBatchingUpdates 为 true,因此并不会直接执行更新 state,而是加入了 dirtyComponents,因此打印时获取的都是更新前的状态 0。 二、两次 setState 时,获取到 this.state.val 都是 0,因此执行时都是将 0 设置成 1,在 react 内部会被合并掉,只执行一次。设置完成后 state.val 值为 1。 三、setTimeout 中的代码,触发时 isBatchingUpdates 为 false,因此可以直接进行更新,因此连着输出 2,3。 输出: 0 0 2 3
参考答案
虚拟dom至关于在js和真实dom中间加了一个缓存,利用dom diff算法避免了没有必要的dom操做,从而提升性能。
具体实现步骤以下:
用 JavaScript 对象结构表示 DOM 树的结构;而后用这个树构建一个真正的 DOM 树,插到文档当中
当状态变动的时候,从新构造一棵新的对象树。而后用新的树和旧的树进行比较,记录两棵树差别
把2所记录的差别应用到步骤1所构建的真正的DOM树上,视图就更新了。
参考答案
结构:
display:none: 会让元素彻底从渲染树中消失,渲染的时候不占据任何空间, 不能点击,
visibility: hidden:不会让元素从渲染树消失,渲染元素继续占据空间,只是内容不可见,不能点击
opacity: 0: 不会让元素从渲染树消失,渲染元素继续占据空间,只是内容不可见,能够点击
继承:
display: none:是非继承属性,子孙节点消失因为元素从渲染树消失形成,经过修改子孙节点属性没法显示。
visibility: hidden:是继承属性,子孙节点消失因为继承了hidden,经过设置visibility: visible;可让子孙节点显式。
性能:
displaynone : 修改元素会形成文档回流,读屏器不会读取display: none元素内容,性能消耗较大
visibility:hidden: 修改元素只会形成本元素的重绘,性能消耗较少读屏器读取visibility: hidden元素内容
opacity: 0 : 修改元素会形成重绘,性能消耗较少
联系:它们都能让元素不可见
参考答案
经常使用的通常为三种.clearfix
, clear:both
,overflow:hidden
;
比较好是 .clearfix
,伪元素万金油版本,后二者有局限性.
.clearfix:after { visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; } <!-- 为毛没有 zoom ,_height 这些,IE6,7这类须要 csshack 再也不咱们考虑以内了 .clearfix 还有另一种写法, --> .clearfix:before, .clearfix:after { content:""; display:table; } .clearfix:after{ clear:both; overflow:hidden; } .clearfix{ zoom:1; } <!-- 用display:table 是为了不外边距margin重叠致使的margin塌陷, 内部元素默认会成为 table-cell 单元格的形式 -->
clear:both
:如果用在同一个容器内相邻元素上,那是贼好的,有时候在容器外就有些问题了, 好比相邻容器的包裹层元素塌陷
overflow:hidden
:这种如果用在同个容器内,能够造成 BFC
避免浮动形成的元素塌陷
参考答案
概念:将多个小图片拼接到一个图片中。经过 background-position 和元素尺寸调节须要显示的背景图案。
优势:
缺点:
link
与@import
的区别参考答案
link
是 HTML 方式, @import
是 CSS 方式link
最大限度支持并行下载,@import
过多嵌套致使串行下载,出现FOUC link
能够经过rel="alternate stylesheet"
指定候选样式link
支持早于@import
,可使用@import
对老浏览器隐藏样式@import
必须在样式规则以前,能够在 css 文件中引用其余文件display: block;
和display: inline;
的区别参考答案
block
元素特色:
1.处于常规流中时,若是width
没有设置,会自动填充满父容器 2.能够应用margin/padding
3.在没有设置高度的状况下会扩展高度以包含常规流中的子元素 4.处于常规流中时布局时在先后元素位置之间(独占一个水平空间) 5.忽略vertical-align
inline
元素特色
1.水平方向上根据direction
依次布局
2.不会在元素先后进行换行
3.受white-space
控制
4.margin/padding
在竖直方向上无效,水平方向上有效
5.width/height
属性对非替换行内元素无效,宽度由元素内容决定
6.非替换行内元素的行框高由line-height
肯定,替换行内元素的行框高由height
,margin
,padding
,border
决定
7.浮动或绝对定位时会转换为block
8.vertical-align
属性生效
参考答案
clear: both
/** * 在标准浏览器下使用 * 1 content内容为空格用于修复opera下文档中出现 * contenteditable属性时在清理浮动元素上下的空白 * 2 使用display使用table而不是block:能够防止容器和 * 子元素top-margin折叠,这样能使清理效果与BFC,IE6/7 * zoom: 1;一致 **/ .clearfix:before, .clearfix:after { content: " "; /* 1 */ display: table; /* 2 */ } .clearfix:after { clear: both; } /** * IE 6/7下使用 * 经过触发hasLayout实现包含浮动 **/ .clearfix { *zoom: 1; }
参考答案
GIF:
JPEG:
PNG:
参考答案
display
为 none,那么 position 和 float 都不起做用,这种状况下元素不产生框参考答案
text-align: center;
便可实现text-align: center;
,再给子元素恢复须要的值<body> <div class="content"> aaaaaa aaaaaa a a a a a a a a </div> </body> <style> body { background: #DDD; text-align: center; /* 3 */ } .content { width: 500px; /* 1 */ text-align: left; /* 3 */ margin: 0 auto; /* 2 */ background: purple; } </style>
若是须要居中的元素为浮动元素,1)为元素设置宽度,2)position: relative;
,3)浮动方向偏移量(left 或者 right)设置为 50%,4)浮动方向上的 margin 设置为元素宽度一半乘以-1
<body> <div class="content"> aaaaaa aaaaaa a a a a a a a a </div> </body> <style> body { background: #DDD; } .content { width: 500px; /* 1 */ float: left; position: relative; /* 2 */ left: 50%; /* 3 */ margin-left: -250px; /* 4 */ background-color: purple; } </style>
若是须要居中的元素为绝对定位元素,1)为元素设置宽度,2)偏移量设置为 50%,3)偏移方向外边距设置为元素宽度一半乘以-1
<body> <div class="content"> aaaaaa aaaaaa a a a a a a a a </div> </body> <style> body { background: #DDD; position: relative; } .content { width: 800px; position: absolute; left: 50%; margin-left: -400px; background-color: purple; } </style>
若是须要居中的元素为绝对定位元素,1)为元素设置宽度,2)设置左右偏移量都为 0,3)设置左右外边距都为 auto
<body> <div class="content"> aaaaaa aaaaaa a a a a a a a a </div> </body> <style> body { background: #DDD; position: relative; } .content { width: 800px; position: absolute; margin: 0 auto; left: 0; right: 0; background-color: purple; } </style>
参考答案
七种数据类型
(ES6以前)其中5种为基本类型:string
,number
,boolean
,null
,undefined
,
ES6出来的Symbol
也是原始数据类型 ,表示独一无二的值
Object
为引用类型(范围挺大),也包括数组、函数,
参考答案
const promise = new Promise((resolve, reject) => { console.log(1) resolve() console.log(2) }) promise.then(() => { console.log(3) }) console.log(4)
输出结果是:
1 2 4 3 promise构造函数是同步执行的,then方法是异步执行的 Promise new的时候会当即执行里面的代码 then是微任务 会在本次任务执行完的时候执行 setTimeout是宏任务 会在下次任务执行的时候执行
参考答案
工厂模式
简单的工厂模式能够理解为解决多个类似的问题;
function CreatePerson(name,age,sex) { var obj = new Object(); obj.name = name; obj.age = age; obj.sex = sex; obj.sayName = function(){ return this.name; } return obj; } var p1 = new CreatePerson("longen",'28','男'); var p2 = new CreatePerson("tugenhua",'27','女'); console.log(p1.name); // longen console.log(p1.age); // 28 console.log(p1.sex); // 男 console.log(p1.sayName()); // longen console.log(p2.name); // tugenhua console.log(p2.age); // 27 console.log(p2.sex); // 女 console.log(p2.sayName()); // tugenhua
单例模式
只能被实例化(构造函数给实例添加属性与方法)一次
// 单体模式 var Singleton = function(name){ this.name = name; }; Singleton.prototype.getName = function(){ return this.name; } // 获取实例对象 var getInstance = (function() { var instance = null; return function(name) { if(!instance) {//至关于一个一次性阀门,只能实例化一次 instance = new Singleton(name); } return instance; } })(); // 测试单体模式的实例,因此a===b var a = getInstance("aa"); var b = getInstance("bb");
沙箱模式
将一些函数放到自执行函数里面,但要用闭包暴露接口,用变量接收暴露的接口,再调用里面的值,不然没法使用里面的值
let sandboxModel=(function(){ function sayName(){}; function sayAge(){}; return{ sayName:sayName, sayAge:sayAge } })()
发布者订阅模式
就例如如咱们关注了某一个公众号,而后他对应的有新的消息就会给你推送,
//发布者与订阅模式 var shoeObj = {}; // 定义发布者 shoeObj.list = []; // 缓存列表 存放订阅者回调函数 // 增长订阅者 shoeObj.listen = function(fn) { shoeObj.list.push(fn); // 订阅消息添加到缓存列表 } // 发布消息 shoeObj.trigger = function() { for (var i = 0, fn; fn = this.list[i++];) { fn.apply(this, arguments);//第一个参数只是改变fn的this, } } // 小红订阅以下消息 shoeObj.listen(function(color, size) { console.log("颜色是:" + color); console.log("尺码是:" + size); }); // 小花订阅以下消息 shoeObj.listen(function(color, size) { console.log("再次打印颜色是:" + color); console.log("再次打印尺码是:" + size); }); shoeObj.trigger("红色", 40); shoeObj.trigger("黑色", 42);
代码实现逻辑是用数组存贮订阅者, 发布者回调函数里面通知的方式是遍历订阅者数组,并将发布者内容传入订阅者数组
参考答案
1.字面量
let obj={'name':'张三'}
2.Object构造函数建立
let Obj=new Object() Obj.name='张三'
3.使用工厂模式建立对象
function createPerson(name){ var o = new Object(); o.name = name; }; return o; } var person1 = createPerson('张三');
4.使用构造函数建立对象
function Person(name){ this.name = name; } var person1 = new Person('张三');
参考答案
HTML中与javascript交互是经过事件驱动来实现的,例如鼠标点击事件onclick、页面的滚动事件onscroll等等,能够向文档或者文档中的元素添加事件侦听器来预订事件。想要知道这些事件是在何时进行调用的,就须要了解一下“事件流”的概念。
什么是事件流:事件流描述的是从页面中接收事件的顺序,DOM2级事件流包括下面几个阶段。
addEventListener:addEventListener是DOM2 级事件新增的指定事件处理程序的操做,这个方法接收3个参数:要处理的事件名、做为事件处理程序的函数和一个布尔值。最后这个布尔值参数若是是true,表示在捕获阶段调用事件处理程序;若是是false,表示在冒泡阶段调用事件处理程序。
IE只支持事件冒泡。
Function._proto_(getPrototypeOf)是什么?
参考答案
获取一个对象的原型,在chrome中能够经过__proto__的形式,或者在ES6中能够经过Object.getPrototypeOf的形式。
那么Function.proto是什么么?也就是说Function由什么对象继承而来,咱们来作以下判别。
Function.__proto__==Object.prototype //false Function.__proto__==Function.prototype//true
咱们发现Function的原型也是Function。
咱们用图能够来明确这个关系:
参考答案
(prototype)
: 一个简单的对象,用于实现对象的 属性继承。能够简单的理解成对象的爹。在 Firefox 和 Chrome 中,每一个JavaScript
对象中都包含一个__proto__
(非标准)的属性指向它爹(该对象的原型),可obj.__proto__
进行访问。new
来 新建一个对象的函数。new
建立出来的对象,即是实例。 实例经过__proto__指向原型,经过constructor指向构造函数。这里来举个栗子,以Object
为例,咱们经常使用的Object
即是一个构造函数,所以咱们能够经过它构建实例。
// 实例 const instance = new Object()
则此时, 实例为instance, 构造函数为Object,咱们知道,构造函数拥有一个prototype
的属性指向原型,所以原型为:
// 原型 const prototype = Object.prototype
这里咱们能够来看出三者的关系:
实例.__proto__ === 原型 原型.constructor === 构造函数 构造函数.prototype === 原型 // 这条线实际上是是基于原型进行获取的,能够理解成一条基于原型的映射线 // 例如: // const o = new Object() // o.constructor === Object --> true // o.__proto__ = null; // o.constructor === Object --> false 实例.constructor === 构造函数
参考答案
在 JS 中,继承一般指的即是 原型链继承,也就是经过指定原型,并能够经过原型链继承原型上的属性或者方法。
最优化: 圣杯模式
var inherit = (function(c,p){ var F = function(){}; return function(c,p){ F.prototype = p.prototype; c.prototype = new F(); c.uber = p.prototype; c.prototype.constructor = c; } })();
class / extends
参考答案
在函数式编程中,函数是一等公民。那么函数柯里化是怎样的呢?
函数柯里化指的是将可以接收多个参数的函数转化为接收单一参数的函数,而且返回接收余下参数且返回结果的新函数的技术。
函数柯里化的主要做用和特色就是参数复用、提早返回和延迟执行。
在一个函数中,首先填充几个参数,而后再返回一个新的函数的技术,称为函数的柯里化。一般可用于在不侵入函数的前提下,为函数 预置通用参数,供屡次重复调用。
const add = function add(x) { return function (y) { return x + y } } const add1 = add(1) add1(2) === 3 add1(20) === 21
参考答案
call
和 apply
都是为了解决改变 this
的指向。做用都是相同的,只是传参的方式不一样。
除了第一个参数外,call
能够接收一个参数列表,apply
只接受一个参数数组。
let a = { value: 1 } function getValue(name, age) { console.log(name) console.log(age) console.log(this.value) } getValue.call(a, 'yck', '24') getValue.apply(a, ['yck', '24'])
bind
和其余两个方法做用也是一致的,只是该方法会返回一个函数。而且咱们能够经过 bind
实现柯里化。
(下面是对这三个方法的扩展介绍)
如何实现一个 bind 函数
对于实现如下几个函数,能够从几个方面思考
window
Function.prototype.myBind = function (context) { if (typeof this !== 'function') { throw new TypeError('Error') } var _this = this var args = [...arguments].slice(1) // 返回一个函数 return function F() { // 由于返回了一个函数,咱们能够 new F(),因此须要判断 if (this instanceof F) { return new _this(...args, ...arguments) } return _this.apply(context, args.concat(...arguments)) } }
如何实现一个call函数
Function.prototype.myCall = function (context) { var context = context || window // 给 context 添加一个属性 // getValue.call(a, 'yck', '24') => a.fn = getValue context.fn = this // 将 context 后面的参数取出来 var args = [...arguments].slice(1) // getValue.call(a, 'yck', '24') => a.fn('yck', '24') var result = context.fn(...args) // 删除 fn delete context.fn return result }
如何实现一个apply函数
Function.prototype.myApply = function (context) { var context = context || window context.fn = this var result // 须要判断是否存储第二个参数 // 若是存在,就将第二个参数展开 if (arguments[1]) { result = context.fn(...arguments[1]) } else { result = context.fn() } delete context.fn return result }
参考答案
function a() { return () => { return () => { console.log(this) } } } console.log(a()()())
箭头函数实际上是没有 this
的,这个函数中的 this
只取决于他外面的第一个不是箭头函数的函数的 this
。在这个例子中,由于调用 a
符合前面代码中的第一个状况,因此 this
是 window
。而且 this
一旦绑定了上下文,就不会被任何代码改变。
function sayHi() { console.log(name); console.log(age); var name = "Lydia"; let age = 21; } sayHi();
Lydia
和 undefined
Lydia
和 ReferenceError
ReferenceError
和 21
undefined
和 ReferenceError
参考答案
在函数中,咱们首先使用var
关键字声明了name
变量。 这意味着变量在建立阶段会被提高(JavaScript
会在建立变量建立阶段为其分配内存空间),默认值为undefined
,直到咱们实际执行到使用该变量的行。 咱们尚未为name
变量赋值,因此它仍然保持undefined
的值。
使用let
关键字(和const
)声明的变量也会存在变量提高,但与var
不一样,初始化没有被提高。 在咱们声明(初始化)它们以前,它们是不可访问的。 这被称为“暂时死区”。 当咱们在声明变量以前尝试访问变量时,JavaScript
会抛出一个ReferenceError
。
关于let
的是否存在变量提高,咱们何以用下面的例子来验证:
let name = 'ConardLi' { console.log(name) // Uncaught ReferenceError: name is not defined let name = 'code秘密花园' }
let
变量若是不存在变量提高,console.log(name)
就会输出ConardLi
,结果却抛出了ReferenceError
,那么这很好的说明了,let
也存在变量提高,可是它存在一个“暂时死区”,在变量未初始化或赋值前不容许访问。
变量的赋值能够分为三个阶段:
undefined
关于let
、var
和function
:
let
的「建立」过程被提高了,可是初始化没有提高。var
的「建立」和「初始化」都被提高了。function
的「建立」「初始化」和「赋值」都被提高了。var a = 10; (function () { console.log(a) a = 5 console.log(window.a) var a = 20; console.log(a) })()
依次输出:undefined -> 10 -> 20
在当即执行函数中,var a = 20; 语句定义了一个局部变量 a,因为js的变量声明提高机制,局部变量a的声明会被提高至当即执行函数的函数体最上方,且因为这样的提高并不包括赋值,所以第一条打印语句会打印undefined,最后一条语句会打印20。 因为变量声明提高,a = 5; 这条语句执行时,局部的变量a已经声明,所以它产生的效果是对局部的变量a赋值,此时window.a 依旧是最开始赋值的10,
class Chameleon { static colorChange(newColor) { this.newColor = newColor; } constructor({ newColor = "green" } = {}) { this.newColor = newColor; } } const freddie = new Chameleon({ newColor: "purple" }); freddie.colorChange("orange");
orange
purple
green
TypeError
答案: D
colorChange
方法是静态的。 静态方法仅在建立它们的构造函数中存在,而且不能传递给任何子级。 因为freddie
是一个子级对象,函数不会传递,因此在freddie
实例上不存在freddie
方法:抛出TypeError
。
var a = ?; if(a == 1 && a == 2 && a == 3){ conso.log(1); }
参考答案
由于==会进行隐式类型转换 因此咱们重写toString方法就能够了
var a = { i: 1, toString() { return a.i++; } } if( a == 1 && a == 2 && a == 3 ) { console.log(1); }
var obj = { '2': 3, '3': 4, 'length': 2, 'splice': Array.prototype.splice, 'push': Array.prototype.push } obj.push(1) obj.push(2) console.log(obj)
参考答案
1.使用第一次push,obj对象的push方法设置 obj[2]=1;obj.length+=1
2.使用第二次push,obj对象的push方法设置 obj[3]=2;obj.length+=1
3.使用console.log输出的时候,由于obj具备 length 属性和 splice 方法,故将其做为数组进行打印
4.打印时由于数组未设置下标为 0 1 处的值,故打印为empty,主动 obj[0] 获取为 undefined
var a = {n: 1}; var b = a; a.x = a = {n: 2}; console.log(a.x) console.log(b.x)
参考答案
undefined
{n:2}
首先,a和b同时引用了{n:2}对象,接着执行到a.x = a = {n:2}语句,尽管赋值是从右到左的没错,可是.的优先级比=要高,因此这里首先执行a.x,至关于为a(或者b)所指向的{n:1}对象新增了一个属性x,即此时对象将变为{n:1;x:undefined}。以后按正常状况,从右到左进行赋值,此时执行a ={n:2}的时候,a的引用改变,指向了新对象{n:2},而b依然指向的是旧对象。以后执行a.x = {n:2}的时候,并不会从新解析一遍a,而是沿用最初解析a.x时候的a,也即旧对象,故此时旧对象的x的值为{n:2},旧对象为 {n:1;x:{n:2}},它被b引用着。
后面输出a.x的时候,又要解析a了,此时的a是指向新对象的a,而这个新对象是没有x属性的,故访问时输出undefined;而访问b.x的时候,将输出旧对象的x的值,即{n:2}。
function checkAge(data) { if (data === { age: 18 }) { console.log("You are an adult!"); } else if (data == { age: 18 }) { console.log("You are still an adult."); } else { console.log(`Hmm.. You don't have an age I guess`); } } checkAge({ age: 18 });
参考答案
Hmm.. You don't have an age I guess
在比较相等性,原始类型经过它们的值进行比较,而对象经过它们的引用进行比较。JavaScript
检查对象是否具备对内存中相同位置的引用。
咱们做为参数传递的对象和咱们用于检查相等性的对象在内存中位于不一样位置,因此它们的引用是不一样的。
这就是为何{ age: 18 } === { age: 18 }
和 { age: 18 } == { age: 18 }
返回 false
的缘由。
const obj = { 1: "a", 2: "b", 3: "c" }; const set = new Set([1, 2, 3, 4, 5]); obj.hasOwnProperty("1"); obj.hasOwnProperty(1); set.has("1"); set.has(1);
参考答案
true
true
false
true
全部对象键(不包括Symbols
)都会被存储为字符串,即便你没有给定字符串类型的键。 这就是为何obj.hasOwnProperty('1')
也返回true
。
上面的说法不适用于Set
。 在咱们的Set
中没有“1”
:set.has('1')
返回false
。 它有数字类型1
,set.has(1)
返回true
。
// example 1 var a={}, b='123', c=123; a[b]='b'; a[c]='c'; console.log(a[b]); --------------------- // example 2 var a={}, b=Symbol('123'), c=Symbol('123'); a[b]='b'; a[c]='c'; console.log(a[b]); --------------------- // example 3 var a={}, b={key:'123'}, c={key:'456'}; a[b]='b'; a[c]='c'; console.log(a[b]);
参考答案
这题考察的是对象的键名的转换。
// example 1 var a={}, b='123', c=123; a[b]='b'; // c 的键名会被转换成字符串'123',这里会把 b 覆盖掉。 a[c]='c'; // 输出 c console.log(a[b]); // example 2 var a={}, b=Symbol('123'), c=Symbol('123'); // b 是 Symbol 类型,不须要转换。 a[b]='b'; // c 是 Symbol 类型,不须要转换。任何一个 Symbol 类型的值都是不相等的,因此不会覆盖掉 b。 a[c]='c'; // 输出 b console.log(a[b]); // example 3 var a={}, b={key:'123'}, c={key:'456'}; // b 不是字符串也不是 Symbol 类型,须要转换成字符串。 // 对象类型会调用 toString 方法转换成字符串 [object Object]。 a[b]='b'; // c 不是字符串也不是 Symbol 类型,须要转换成字符串。 // 对象类型会调用 toString 方法转换成字符串 [object Object]。这里会把 b 覆盖掉。 a[c]='c'; // 输出 c console.log(a[b]);
(() => { let x, y; try { throw new Error(); } catch (x) { (x = 1), (y = 2); console.log(x); } console.log(x); console.log(y); })();
参考答案
1
undefined
2
catch
块接收参数x
。当咱们传递参数时,这与变量的x
不一样。这个变量x
是属于catch
做用域的。
以后,咱们将这个块级做用域的变量设置为1
,并设置变量y
的值。 如今,咱们打印块级做用域的变量x
,它等于1
。
在catch
块以外,x
仍然是undefined
,而y
是2
。 当咱们想在catch
块以外的console.log(x)
时,它返回undefined
,而y
返回2
。
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
function Foo() { Foo.a = function() { console.log(1) } this.a = function() { console.log(2) } } // 以上只是 Foo 的构建方法,没有产生实例,此刻也没有执行 Foo.prototype.a = function() { console.log(3) } // 如今在 Foo 上挂载了原型方法 a ,方法输出值为 3 Foo.a = function() { console.log(4) } // 如今在 Foo 上挂载了直接方法 a ,输出值为 4 Foo.a(); // 马上执行了 Foo 上的 a 方法,也就是刚刚定义的,因此 // # 输出 4 let obj = new Foo(); /* 这里调用了 Foo 的构建方法。Foo 的构建方法主要作了两件事: 1. 将全局的 Foo 上的直接方法 a 替换为一个输出 1 的方法。 2. 在新对象上挂载直接方法 a ,输出值为 2。 */ obj.a(); // 由于有直接方法 a ,不须要去访问原型链,因此使用的是构建方法里所定义的 this.a, // # 输出 2 Foo.a(); // 构建方法里已经替换了全局 Foo 上的 a 方法,因此 // # 输出 1