乐趣区

关于前端:社招中级前端笔试面试题总结

typeof null 的后果是什么,为什么?

typeof null 的后果是 Object。

在 JavaScript 第一个版本中,所有值都存储在 32 位的单元中,每个单元蕴含一个小的 类型标签(1-3 bits) 以及以后要存储值的实在数据。类型标签存储在每个单元的低位中,共有五种数据类型:

000: object   - 以后存储的数据指向一个对象。1: int      - 以后存储的数据是一个 31 位的有符号整数。010: double   - 以后存储的数据指向一个双精度的浮点数。100: string   - 以后存储的数据指向一个字符串。110: boolean  - 以后存储的数据是布尔值。

如果最低位是 1,则类型标签标记位的长度只有一位;如果最低位是 0,则类型标签标记位的长度占三位,为存储其余四种数据类型提供了额定两个 bit 的长度。

有两种非凡数据类型:

  • undefined 的值是 (-2)30(一个超出整数范畴的数字);
  • null 的值是机器码 NULL 指针(null 指针的值全是 0)

那也就是说 null 的类型标签也是 000,和 Object 的类型标签一样,所以会被断定为 Object。

常见浏览器所用内核

(1)IE 浏览器内核:Trident 内核,也是俗称的 IE 内核;

(2)Chrome 浏览器内核:统称为 Chromium 内核或 Chrome 内核,以前是 Webkit 内核,当初是 Blink 内核;

(3)Firefox 浏览器内核:Gecko 内核,俗称 Firefox 内核;

(4)Safari 浏览器内核:Webkit 内核;

(5)Opera 浏览器内核:最后是本人的 Presto 内核,起初退出谷歌大军,从 Webkit 又到了 Blink 内核;

(6)360 浏览器、猎豹浏览器内核:IE + Chrome 双内核;

(7)搜狗、漫游、QQ 浏览器内核:Trident(兼容模式)+ Webkit(高速模式);

(8)百度浏览器、世界之窗内核:IE 内核;

(9)2345 浏览器内核:如同以前是 IE 内核,当初也是 IE + Chrome 双内核了;

(10)UC 浏览器内核:这个众口不一,UC 说是他们本人研发的 U3 内核,但如同还是基于 Webkit 和 Trident,还有说是基于火狐内核。

一个 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 申请数也是能够没有下限地继续发送

对浏览器的了解

浏览器的次要性能是将用户抉择的 web 资源出现进去,它须要从服务器申请资源,并将其显示在浏览器窗口中,资源的格局通常是 HTML,也包含 PDF、image 及其他格局。用户用 URI(Uniform Resource Identifier 对立资源标识符)来指定所申请资源的地位。

HTML 和 CSS 标准中规定了浏览器解释 html 文档的形式,由 W3C 组织对这些标准进行保护,W3C 是负责制订 web 规范的组织。然而浏览器厂商纷纷开发本人的扩大,对标准的遵循并不欠缺,这为 web 开发者带来了重大的兼容性问题。

浏览器能够分为两局部,shell 和 内核。其中 shell 的品种绝对比拟多,内核则比拟少。也有一些浏览器并不辨别外壳和内核。从 Mozilla 将 Gecko 独立进去后,才有了外壳和内核的明确划分。

  • shell 是指浏览器的外壳:例如菜单,工具栏等。次要是提供给用户界面操作,参数设置等等。它是调用内核来实现各种性能的。
  • 内核是浏览器的外围。内核是基于标记语言显示内容的程序或模块。

深浅拷贝

1. 浅拷贝的原理和实现

本人创立一个新的对象,来承受你要从新复制或援用的对象值。如果对象属性是根本的数据类型,复制的就是根本类型的值给新对象;但如果属性是援用数据类型,复制的就是内存中的地址,如果其中一个对象扭转了这个内存中的地址,必定会影响到另一个对象

办法一:object.assign

object.assign是 ES6 中 object 的一个办法,该办法能够用于 JS 对象的合并等多个用处,其中一个用处就是能够进行浅拷贝。该办法的第一个参数是拷贝的指标对象,前面的参数是拷贝的起源对象(也能够是多个起源)。

object.assign 的语法为:Object.assign(target, ...sources)

object.assign 的示例代码如下:

let target = {};
let source = {a: { b: 1} };
Object.assign(target, source);
console.log(target); // {a: { b: 1} };

然而应用 object.assign 办法有几点须要留神

  • 它不会拷贝对象的继承属性;
  • 它不会拷贝对象的不可枚举的属性;
  • 能够拷贝 Symbol 类型的属性。
let obj1 = {a:{ b:1}, sym:Symbol(1)}; 
Object.defineProperty(obj1, 'innumerable' ,{
    value:'不可枚举属性',
    enumerable:false
});
let obj2 = {};
Object.assign(obj2,obj1)
obj1.a.b = 2;
console.log('obj1',obj1);
console.log('obj2',obj2);

从下面的样例代码中能够看到,利用 object.assign 也能够拷贝 Symbol 类型的对象,然而如果到了对象的第二层属性 obj1.a.b 这里的时候,前者值的扭转也会影响后者的第二层属性的值,阐明其中 仍旧存在着拜访独特堆内存的问题 ,也就是说 这种办法还不能进一步复制,而只是实现了浅拷贝的性能

办法二:扩大运算符形式

  • 咱们也能够利用 JS 的扩大运算符,在结构对象的同时实现浅拷贝的性能。
  • 扩大运算符的语法为:let cloneObj = {...obj};
/* 对象的拷贝 */
let obj = {a:1,b:{c:1}}
let obj2 = {...obj}
obj.a = 2
console.log(obj)  //{a:2,b:{c:1}} console.log(obj2); //{a:1,b:{c:1}}
obj.b.c = 2
console.log(obj)  //{a:2,b:{c:2}} console.log(obj2); //{a:1,b:{c:2}}
/* 数组的拷贝 */
let arr = [1, 2, 3];
let newArr = [...arr]; // 跟 arr.slice()是一样的成果

扩大运算符 和 object.assign 有同样的缺点,也就是 实现的浅拷贝的性能差不多 ,然而如果属性都是 根本类型的值,应用扩大运算符进行浅拷贝会更加不便

办法三:concat 拷贝数组

数组的 concat 办法其实也是浅拷贝,所以连贯一个含有援用类型的数组时,须要留神批改原数组中的元素的属性,因为它会影响拷贝之后连贯的数组。不过 concat 只能用于数组的浅拷贝,应用场景比拟局限。代码如下所示。

let arr = [1, 2, 3];
let newArr = arr.concat();
newArr[1] = 100;
console.log(arr);  // [1, 2, 3]
console.log(newArr); // [1, 100, 3]

办法四:slice 拷贝数组

slice 办法也比拟有局限性,因为 它仅仅针对数组类型slice 办法会返回一个新的数组对象,这一对象由该办法的前两个参数来决定原数组截取的开始和完结工夫,是不会影响和扭转原始数组的。

slice 的语法为:arr.slice(begin, end);
let arr = [1, 2, {val: 4}];
let newArr = arr.slice();
newArr[2].val = 1000;
console.log(arr);  //[1, 2, { val: 1000} ]

从下面的代码中能够看出,这就是 浅拷贝的限度所在了——它只能拷贝一层对象 。如果 存在对象的嵌套,那么浅拷贝将无能为力。因而深拷贝就是为了解决这个问题而生的,它能解决多层对象嵌套问题,彻底实现拷贝

手工实现一个浅拷贝

依据以上对浅拷贝的了解,如果让你本人实现一个浅拷贝,大抵的思路分为两点:

  • 对根底类型做一个最根本的一个拷贝;
  • 对援用类型开拓一个新的存储,并且拷贝一层对象属性。
const shallowClone = (target) => {if (typeof target === 'object' && target !== null) {const cloneTarget = Array.isArray(target) ? []: {};
    for (let prop in target) {if (target.hasOwnProperty(prop)) {cloneTarget[prop] = target[prop];
      }
    }
    return cloneTarget;
  } else {return target;}
}

利用类型判断,针对援用类型的对象进行 for 循环遍历对象属性赋值给指标对象的属性,根本就能够手工实现一个浅拷贝的代码了

2. 深拷贝的原理和实现

浅拷贝只是创立了一个新的对象,复制了原有对象的根本类型的值,而援用数据类型只拷贝了一层属性,再深层的还是无奈进行拷贝。深拷贝则不同,对于简单援用数据类型,其在堆内存中齐全开拓了一块内存地址,并将原有的对象齐全复制过去寄存。

这两个对象是互相独立、不受影响的,彻底实现了内存上的拆散。总的来说,深拷贝的原理能够总结如下

将一个对象从内存中残缺地拷贝进去一份给指标对象,并从堆内存中开拓一个全新的空间寄存新对象,且新对象的批改并不会扭转原对象,二者实现真正的拆散。

办法一:乞丐版(JSON.stringify)

JSON.stringify() 是目前开发过程中最简略的深拷贝办法,其实就是把一个对象序列化成为 JSON 的字符串,并将对象外面的内容转换成字符串,最初再用 JSON.parse() 的办法将 JSON 字符串生成一个新的对象

let a = {
    age: 1,
    jobs: {first: 'FE'}
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE

然而该办法也是有局限性的

  • 会疏忽 undefined
  • 会疏忽 symbol
  • 不能序列化函数
  • 无奈拷贝不可枚举的属性
  • 无奈拷贝对象的原型链
  • 拷贝 RegExp 援用类型会变成空对象
  • 拷贝 Date 援用类型会变成字符串
  • 对象中含有 NaNInfinity 以及 -InfinityJSON 序列化的后果会变成 null
  • 不能解决循环援用的对象,即对象成环 (obj[key] = obj)。
function Obj() {this.func = function () {alert(1) }; 
  this.obj = {a:1};
  this.arr = [1,2,3];
  this.und = undefined; 
  this.reg = /123/; 
  this.date = new Date(0); 
  this.NaN = NaN;
  this.infinity = Infinity;
  this.sym = Symbol(1);
} 
let obj1 = new Obj();
Object.defineProperty(obj1,'innumerable',{ 
  enumerable:false,
  value:'innumerable'
});
console.log('obj1',obj1);
let str = JSON.stringify(obj1);
let obj2 = JSON.parse(str);
console.log('obj2',obj2);

应用 JSON.stringify 办法实现深拷贝对象,尽管到目前为止还有很多无奈实现的性能,然而这种办法足以满足日常的开发需要,并且是最简略和快捷的。而对于其余的也要实现深拷贝的,比拟麻烦的属性对应的数据类型,JSON.stringify 临时还是无奈满足的,那么就须要上面的几种办法了

办法二:根底版(手写递归实现)

上面是一个实现 deepClone 函数封装的例子,通过 for in 遍历传入参数的属性值,如果值是援用类型则再次递归调用该函数,如果是根底数据类型就间接复制

let obj1 = {
  a:{b:1}
}
function deepClone(obj) {let cloneObj = {}
  for(let key in obj) {                 // 遍历
    if(typeof obj[key] ==='object') {cloneObj[key] = deepClone(obj[key])  // 是对象就再次调用该函数递归
    } else {cloneObj[key] = obj[key]  // 根本类型的话间接复制值
    }
  }
  return cloneObj
}
let obj2 = deepClone(obj1);
obj1.a.b = 2;
console.log(obj2);   //  {a:{b:1}}

尽管利用递归能实现一个深拷贝,然而同下面的 JSON.stringify 一样,还是有一些问题没有齐全解决,例如:

  • 这个深拷贝函数并不能复制不可枚举的属性以及 Symbol 类型;
  • 这种办法 只是针对一般的援用类型的值做递归复制,而对于 Array、Date、RegExp、Error、Function 这样的援用类型并不能正确地拷贝;
  • 对象的属性外面成环,即 循环援用没有解决

这种根底版本的写法也比较简单,能够应答大部分的利用状况。然而你在面试的过程中,如果只能写出这样的一个有缺点的深拷贝办法,有可能不会通过。

所以为了“援救”这些缺点,上面我带你一起看看改良的版本,以便于你能够在面试种呈现出更好的深拷贝办法,博得面试官的青眼。

办法三:改进版(改良后递归实现)

针对下面几个待解决问题,我先通过四点相干的实践通知你别离应该怎么做。

  • 针对可能遍历对象的不可枚举属性以及 Symbol 类型,咱们能够应用 Reflect.ownKeys 办法;
  • 当参数为 Date、RegExp 类型,则间接生成一个新的实例返回;
  • 利用 ObjectgetOwnPropertyDescriptors 办法能够取得对象的所有属性,以及对应的个性,顺便联合 Object.create 办法创立一个新对象,并继承传入原对象的原型链;
  • 利用 WeakMap 类型作为 Hash 表,因为 WeakMap 是弱援用类型,能够无效避免内存透露(你能够关注一下 MapweakMap 的要害区别,这里要用 weakMap),作为检测循环援用很有帮忙,如果存在循环,则援用间接返回 WeakMap 存储的值

如果你在思考到循环援用的问题之后,还能用 WeakMap 来很好地解决,并且向面试官解释这样做的目标,那么你所展现的代码,以及你对问题思考的全面性,在面试官眼中应该算是合格的了

实现深拷贝

const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)

const deepClone = function (obj, hash = new WeakMap()) {if (obj.constructor === Date) {return new Date(obj)       // 日期对象间接返回一个新的日期对象
  }

  if (obj.constructor === RegExp){return new RegExp(obj)     // 正则对象间接返回一个新的正则对象
  }

  // 如果循环援用了就用 weakMap 来解决
  if (hash.has(obj)) {return hash.get(obj)
  }
  let allDesc = Object.getOwnPropertyDescriptors(obj)

  // 遍历传入参数所有键的个性
  let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)

  // 把 cloneObj 原型复制到 obj 上
  hash.set(obj, cloneObj)

  for (let key of Reflect.ownKeys(obj)) {cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key]
  }
  return cloneObj
}
// 上面是验证代码
let obj = {
  num: 0,
  str: '',
  boolean: true,
  unf: undefined,
  nul: null,
  obj: {name: '我是一个对象', id: 1},
  arr: [0, 1, 2],
  func: function () { console.log('我是一个函数') },
  date: new Date(0),
  reg: new RegExp('/ 我是一个正则 /ig'),
  [Symbol('1')]: 1,
};
Object.defineProperty(obj, 'innumerable', {enumerable: false, value: '不可枚举属性'}
);
obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj))
obj.loop = obj    // 设置 loop 成循环援用的属性
let cloneObj = deepClone(obj)
cloneObj.arr.push(4)
console.log('obj', obj)
console.log('cloneObj', cloneObj)

咱们看一下后果,cloneObjobj 的根底上进行了一次深拷贝,cloneObj 里的 arr 数组进行了批改,并未影响到 obj.arr 的变动,如下图所示

点击刷新按钮或者按 F5、按 Ctrl+F5(强制刷新)、地址栏回车有什么区别?

  • 点击刷新按钮或者按 F5: 浏览器间接对本地的缓存文件过期,然而会带上 If-Modifed-Since,If-None-Match,这就意味着服务器会对文件查看新鲜度,返回后果可能是 304,也有可能是 200。
  • 用户按 Ctrl+F5(强制刷新): 浏览器不仅会对本地文件过期,而且不会带上 If-Modifed-Since,If-None-Match,相当于之前素来没有申请过,返回后果是 200。
  • 地址栏回车:浏览器发动申请,依照失常流程,本地查看是否过期,而后服务器查看新鲜度,最初返回内容。

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

对 this 对象的了解

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

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

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

常见的浏览器内核比拟

  • Trident: 这种浏览器内核是 IE 浏览器用的内核,因为在晚期 IE 占有大量的市场份额,所以这种内核比拟风行,以前有很多网页也是依据这个内核的规范来编写的,然而实际上这个内核对真正的网页规范反对不是很好。然而因为 IE 的高市场占有率,微软也很长时间没有更新 Trident 内核,就导致了 Trident 内核和 W3C 规范脱节。还有就是 Trident 内核的大量 Bug 等平安问题没有失去解决,加上一些专家学者公开本人认为 IE 浏览器不平安的观点,使很多用户开始转向其余浏览器。
  • Gecko: 这是 Firefox 和 Flock 所采纳的内核,这个内核的长处就是功能强大、丰盛,能够反对很多简单网页成果和浏览器扩大接口,然而代价是也不言而喻就是要耗费很多的资源,比方内存。
  • Presto: Opera 已经采纳的就是 Presto 内核,Presto 内核被称为公认的浏览网页速度最快的内核,这得益于它在开发时的天生劣势,在解决 JS 脚本等脚本语言时,会比其余的内核快 3 倍左右,毛病就是为了达到很快的速度而丢掉了一部分网页兼容性。
  • Webkit: Webkit 是 Safari 采纳的内核,它的长处就是网页浏览速度较快,尽管不迭 Presto 然而也胜于 Gecko 和 Trident,毛病是对于网页代码的容错性不高,也就是说对网页代码的兼容性较低,会使一些编写不规范的网页无奈正确显示。WebKit 前身是 KDE 小组的 KHTML 引擎,能够说 WebKit 是 KHTML 的一个开源的分支。
  • Blink: 谷歌在 Chromium Blog 上发表博客,称将与苹果的开源浏览器外围 Webkit 各奔前程,在 Chromium 我的项目中研发 Blink 渲染引擎(即浏览器外围),内置于 Chrome 浏览器之中。其实 Blink 引擎就是 Webkit 的一个分支,就像 webkit 是 KHTML 的分支一样。Blink 引擎当初是谷歌公司与 Opera Software 独特研发,下面提到过的,Opera 弃用了本人的 Presto 内核,退出 Google 营垒,追随谷歌一起研发 Blink。

Virtual Dom 的劣势在哪里?

Virtual Dom 的劣势」其实这道题目面试官更想听到的答案不是上来就说「间接操作 / 频繁操作 DOM 的性能差」,如果 DOM 操作的性能如此不堪,那么 jQuery 也不至于活到明天。所以面试官更想听到 VDOM 想解决的问题以及为什么频繁的 DOM 操作会性能差。

首先咱们须要晓得:

DOM 引擎、JS 引擎 互相独立,但又工作在同一线程(主线程)JS 代码调用 DOM API 必须 挂起 JS 引擎、转换传入参数数据、激活 DOM 引擎,DOM 重绘后再转换可能有的返回值,最初激活 JS 引擎并继续执行若有频繁的 DOM API 调用,且浏览器厂商不做“批量解决”优化,引擎间切换的单位代价将迅速积攒若其中有强制重绘的 DOM API 调用,从新计算布局、从新绘制图像会引起更大的性能耗费。

其次是 VDOM 和实在 DOM 的区别和优化:

  1. 虚构 DOM 不会立马进行排版与重绘操作
  2. 虚构 DOM 进行频繁批改,而后一次性比拟并批改实在 DOM 中须要改的局部,最初在实在 DOM 中进行排版与重绘,缩小过多 DOM 节点排版与重绘损耗
  3. 虚构 DOM 无效升高大面积实在 DOM 的重绘与排版,因为最终与实在 DOM 比拟差别,能够只渲染部分

画一条 0.5px 的线

  • 采纳 transform: scale()的形式,该办法用来定义元素的 2D 缩放转换:
transform: scale(0.5,0.5);
  • 采纳 meta viewport 的形式
<meta name="viewport" content="width=device-width, initial-scale=0.5, minimum-scale=0.5, maximum-scale=0.5"/>

这样就能缩放到原来的 0.5 倍,如果是 1px 那么就会变成 0.5px。viewport 只针对于挪动端,只在挪动端上能力看到成果

CSS 如何阻塞文档解析?

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

冒泡排序 – 工夫复杂度 n^2

题目形容: 实现一个冒泡排序

实现代码如下:

function bubbleSort(arr) {
  // 缓存数组长度
  const len = arr.length;
  // 外层循环用于管制从头到尾的比拟 + 替换到底有多少轮
  for (let i = 0; i < len; i++) {
    // 内层循环用于实现每一轮遍历过程中的反复比拟 + 替换
    for (let j = 0; j < len - 1; j++) {
      // 若相邻元素后面的数比前面的大
      if (arr[j] > arr[j + 1]) {
        // 替换两者
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
      }
    }
  }
  // 返回数组
  return arr;
}
// console.log(bubbleSort([3, 6, 2, 4, 1]));

代码输入后果

var obj = {say: function() {var f1 = () =>  {console.log("1111", this);
     }
     f1();},
   pro: {getPro:() =>  {console.log(this);
     }
   }
}
var o = obj.say;
o();
obj.say();
obj.pro.getPro();

输入后果:

1111 window 对象
1111 obj 对象
window 对象

解析:

  1. o(),o 是在全局执行的,而 f1 是箭头函数,它是没有绑定 this 的,它的 this 指向其父级的 this,其父级 say 办法的 this 指向的是全局作用域,所以会打印出 window;
  2. obj.say(),谁调用 say,say 的 this 就指向谁,所以此时 this 指向的是 obj 对象;
  3. obj.pro.getPro(),咱们晓得,箭头函数时不绑定 this 的,getPro 处于 pro 中,而对象不形成独自的作用域,所以箭头的函数的 this 就指向了全局作用域 window。

JSONP

JSONP 外围原理:script 标签不受同源策略束缚,所以能够用来进行跨域申请,长处是兼容性好,然而只能用于 GET 申请;

const jsonp = ({url, params, callbackName}) => {const generateUrl = () => {
        let dataSrc = ''
        for (let key in params) {if (params.hasOwnProperty(key)) {dataSrc += `${key}=${params[key]}&`
            }
        }
        dataSrc += `callback=${callbackName}`
        return `${url}?${dataSrc}`
    }
    return new Promise((resolve, reject) => {const scriptEle = document.createElement('script')
        scriptEle.src = generateUrl()
        document.body.appendChild(scriptEle)
        window[callbackName] = data => {resolve(data)
            document.removeChild(scriptEle)
        }
    })
}

HTTP 1.0 和 HTTP 1.1 之间有哪些区别?

HTTP 1.0 和 HTTP 1.1 有以下区别

  • 连贯方面,http1.0 默认应用非长久连贯,而 http1.1 默认应用长久连贯。http1.1 通过应用长久连贯来使多个 http 申请复用同一个 TCP 连贯,以此来防止应用非长久连贯时每次须要建设连贯的时延。
  • 资源申请方面,在 http1.0 中,存在一些节约带宽的景象,例如客户端只是须要某个对象的一部分,而服务器却将整个对象送过来了,并且不反对断点续传性能,http1.1 则在申请头引入了 range 头域,它容许只申请资源的某个局部,即返回码是 206(Partial Content),这样就不便了开发者自在的抉择以便于充分利用带宽和连贯。
  • 缓存方面,在 http1.0 中次要应用 header 里的 If-Modified-Since、Expires 来做为缓存判断的规范,http1.1 则引入了更多的缓存控制策略,例如 Etag、If-Unmodified-Since、If-Match、If-None-Match 等更多可供选择的缓存头来管制缓存策略。
  • http1.1 中 新增了 host 字段,用来指定服务器的域名。http1.0 中认为每台服务器都绑定一个惟一的 IP 地址,因而,申请音讯中的 URL 并没有传递主机名(hostname)。但随着虚拟主机技术的倒退,在一台物理服务器上能够存在多个虚拟主机,并且它们共享一个 IP 地址。因而有了 host 字段,这样就能够将申请发往到同一台服务器上的不同网站。
  • http1.1 绝对于 http1.0 还新增了很多 申请办法,如 PUT、HEAD、OPTIONS 等。

call() 和 apply() 的区别?

它们的作用截然不同,区别仅在于传入参数的模式的不同。

  • apply 承受两个参数,第一个参数指定了函数体内 this 对象的指向,第二个参数为一个带下标的汇合,这个汇合能够为数组,也能够为类数组,apply 办法把这个汇合中的元素作为参数传递给被调用的函数。
  • call 传入的参数数量不固定,跟 apply 雷同的是,第一个参数也是代表函数体内的 this 指向,从第二个参数开始往后,每个参数被顺次传入函数。

let、const、var 的区别

(1)块级作用域: 块作用域由 {}包含,let 和 const 具备块级作用域,var 不存在块级作用域。块级作用域解决了 ES5 中的两个问题:

  • 内层变量可能笼罩外层变量
  • 用来计数的循环变量泄露为全局变量

(2)变量晋升: var 存在变量晋升,let 和 const 不存在变量晋升,即在变量只能在申明之后应用,否在会报错。

(3)给全局增加属性: 浏览器的全局对象是 window,Node 的全局对象是 global。var 申明的变量为全局变量,并且会将该变量增加为全局对象的属性,然而 let 和 const 不会。

(4)反复申明: var 申明变量时,能够反复申明变量,后申明的同名变量会笼罩之前申明的遍历。const 和 let 不容许反复申明变量。

(5)暂时性死区: 在应用 let、const 命令申明变量之前,该变量都是不可用的。这在语法上,称为 暂时性死区。应用 var 申明的变量不存在暂时性死区。

(6)初始值设置: 在变量申明时,var 和 let 能够不必设置初始值。而 const 申明变量必须设置初始值。

(7)指针指向: let 和 const 都是 ES6 新增的用于创立变量的语法。let 创立的变量是能够更改指针指向(能够从新赋值)。但 const 申明的变量是不容许扭转指针的指向。

区别 var let const
是否有块级作用域 × ✔️ ✔️
是否存在变量晋升 ✔️ × ×
是否增加全局属性 ✔️ × ×
是否反复申明变量 ✔️ × ×
是否存在暂时性死区 × ✔️ ✔️
是否必须设置初始值 × × ✔️
是否扭转指针指向 ✔️ ✔️ ×

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

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

如何优化动画?

对于如何优化动画,咱们晓得,个别状况下,动画须要频繁的操作 DOM,就就会导致页面的性能问题,咱们能够将动画的 position 属性设置为 absolute 或者fixed,将动画脱离文档流,这样他的回流就不会影响到页面了。

抉择排序 – 工夫复杂度 n^2

题目形容: 实现一个抉择排序

实现代码如下:

function selectSort(arr) {
  // 缓存数组长度
  const len = arr.length;
  // 定义 minIndex,缓存以后区间最小值的索引,留神是索引
  let minIndex;
  // i 是以后排序区间的终点
  for (let i = 0; i < len - 1; i++) {
    // 初始化 minIndex 为以后区间第一个元素
    minIndex = i;
    // i、j 别离定义以后区间的上下界,i 是左边界,j 是右边界
    for (let j = i; j < len; j++) {
      // 若 j 处的数据项比以后最小值还要小,则更新最小值索引为 j
      if (arr[j] < arr[minIndex]) {minIndex = j;}
    }
    // 如果 minIndex 对应元素不是目前的头部元素,则替换两者
    if (minIndex !== i) {[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
    }
  }
  return arr;
}
// console.log(quickSort([3, 6, 2, 4, 1]));
退出移动版