面试题联盟之 JS 篇
关注 入坑互联网 ,回复“加群”
加入我们一起学习,天天进步
1.闭包理解
父函数将子函数作为返回值,再将子函数赋值给一个变量,所以子函数会存在于内存中,而子函数依赖于父函数存在,所以父函数也会存在于内存中,也就不会被垃圾回收机制回收。
1: let val = 7
2: function createAdder() {
3: function addNumbers(a, b) {
4: let ret = a + b
5: return ret
6: }
7: return addNumbers
8:}
9: let adder = createAdder()
10: let sum = adder(val, 8)
11: console.log('example of function returning a function: ', sum)
2.继承链的理解(原型链)
每一个对象都有一个“proto”指针,指向实例化该对象的构造函数的原型对象,当该对象没有你想拿到的属性时,解释器会顺着指针不断向上找。
每一个构造你函数都有一个“prototype”属性,指向该构造函数的原型对象。
每一个原型对象都有一个“constructor”属性,指向该原型对象的构造函数。
任何对象(全局对象除外)向上查找的终点都是全局对象下的 Object 构造函数的原型对象。
3.对像合并?数组去重过滤?
对象合并:Object.assign(form, obj)----->Object.assign(目标对象, 被合并的对象)、$.extend、
数组去重:Set、filter、Map
4.http 状态码有哪几种?常用的状态码表示什么?
200("OK")
一切正常。实体主体中的文档(若存在的话)是某资源的表示。
400("Bad Request")
客户端方面的问题。实体主题中的文档(若存在的话)是一个错误消息。希望客户端能够理解此错误消息,并改正问题。
500("Internal Server Error")
服务期方面的问题。实体主体中的文档(如果存在的话)是一个错误消息。该错误消息通常无济于事,因为客户端无法修复服务器方面的问题。
301("Moved Permanently")
当客户端触发的动作引起了资源 URI 的变化时发送此响应代码。另外,当客户端向一个资源的旧 URI 发送请求时,也发送此响应代码。
404("Not Found") 和 410("Gone")
当客户端所请求的 URI 不对应于任何资源时,发送此响应代码。404 用于服务器端不知道客户端要请求哪个资源的情况;410 用于服务器端知道客户端所请求的资源曾经存在,但现在已经不存在了的情况。
409("Conflict")
当客户端试图执行一个”会导致一个或多个资源处于不一致状态“的操作时,发送此响应代码。
5.跨域的理解?常见的跨域处理有哪一些?\
浏览器为隔离潜在的恶意文件,限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互,所以,是浏览器的基于安全考虑的同源策略导致的跨域
解决一:
JSONP,script 的 src 属性不受限制,但是只能进行 get 请求,
jQuery 将 jsonp 封装进了 ajax,首先 jsonp 只支持 get 请求,所以所有传入的参数都是 http://xxx.xxx.xxx/xxx/xxx?xxx=1&&yyy=2 这种形式;其次在 dataType 属性必须设置为 jsonp,jquery 是支持 jsonp 的。
$.ajax("http://xxx.xxx.xxx/xxx/xxx", {
type: 'get',
dataType: "jsonp",
data : reqData
success: function(data) {
console.log(data);
},
error: function(xhr, type, errorThrown) {
console.log(xhr.statusText);
plus.nativeUI.toast("fail");
}
});
解决二:直接在服务器端设置跨域资源访问 CORS(Cross-Origin Resource Sharing),设置 Request Header 头中 Access-Control-Allow-Origin 为指定可获取数据的域名(常用)
解决三:使用 Nginx 反向代理
解决四:webpack 代理(只适用于本地环境)
解决五:nodejs 反向代理
6.seo 优化的理解?(加分项)
① 提高页面加载速度。能用 css 解决的不用背景图片,背景图片也尽量压缩大小,可以几个 icons 放在一个图片上,使用 background-position 找到需要的图片位置。可以减少 HTTP 请求数,提高网页加载速度。
② 结构、表现和行为的分离。另外一个重要的拖慢网页加载速度的原因就是将 css 和 JS 都堆积在 HTML 页面上,每次看到有人直接在页面上编写 CSS 和 JS 我都很痛心疾首。通过外链的方式能大大加快网页加载速度的,css 文件可以放在 head 里,JS 文件可以放置在 body 的最下方,在不影响阅读的情况下再去加载 JS 文件。
③ 优化网站分级结构。在每个内页加面包屑导航是很有必要的,可以让蜘蛛进入页面之后不至于迷路,有条件的话,最好能单独加个 Sitemap 页面,将网站结构一目了然地展示在蜘蛛面前,更有利于蜘蛛抓取信息。
④ 集中网站权重。由于蜘蛛分配到每个页面的权重是一定的,这些权重也将平均分配到每个 a 链接上,那么为了集中网站权重,可以使用”rel=nofollow”属性,它告诉蜘蛛无需抓取目标页,可以将权重分给其他的链接。
⑤ 文本强调标签的使用。当着重强调某个关键词需要加粗表示,选用 strong 标签比使用 b 标签要更有强调作用。
⑥ a 标签的 title 属性的使用。在不影响页面功能的情况下,可以尽量给 a 标签加上 title 属性,可以更有利于蜘蛛抓取信息。
⑦ 图片 alt 属性的使用。这个属性可以在图片加载不出来的时候显示在页面上相关的文字信息,作用同上。
⑧ H 标签的使用。主要是 H1 标签的使用需要特别注意,因为它自带权重,一个页面有且最多只能有一个 H1 标签,放在该页面最重要的标题上面,如首页的 logo 上可以加 H1 标签。
7.对象深拷贝、浅拷贝
浅拷贝:当拷贝完一个对象的时候,其中一个对象的数据发生了变化,另一个对象的数据也会发生变化,因为浅拷贝拷贝的是索引
深拷贝:当拷贝完一个对象的时候,其中一个对象的数据发生了变化,另外一个对象的数据 不会发生变化,因为深拷贝拷贝的是数值
8.js 异步加载的方式
① <script>标签的async="async"属性
<script type="text/javascript" src="xxx.js"async="async"></script>
② onload时的异步加载(这种方法只是把插入script的方法放在一个函数里面,然后放在window的onload方法里面执行,这样就解决了阻塞onload事件触发的问题。)
③ $(document).ready(function() {alert("加载完成!") })
④ <script>标签的defer="defer"属性
<script type="text/javascript" defer></script>
9.babel 原理
ES6、7代码输入 -> babylon进行解析 -> 得到AST(抽象语法树)-> plugin用babel-traverse对AST树进行遍历转译 ->得到新的AST树->用babel-generator通过AST树生成ES5代码
10.JavaScript 中的强制转型(coercion)是指什么?
在 JavaScript 中,两种不同的内置类型间的转换被称为强制转型。强制转型在 JavaScript 中有两种形式:显式和隐式。
显式强制转型
var a = "42";
var b = Number( a );
a; // "42" -- 字符串
b; // 42 -- 是个数字!
这是一个隐式强制转型的例子:
var a = "42";
var b = a * 1; // "42" 隐式转型成 42
a; // "42"
b; // 42 -- 是个数字!
11.JavaScript 中的作用域(scope)是指什么?
在 JavaScript 中,每个函数都有自己的作用域。作用域基本上是变量以及如何通过名称访问这些变量的规则的集合。只有函数中的代码才能访问函数作用域内的变量。
同一个作用域中的变量名必须是唯一的。一个作用域可以嵌套在另一个作用域内。如果一个作用域嵌套在另一个作用域内,最内部作用域内的代码可以访问另一个作用域的变量。
12.“use strict”的作用是什么?
use strict 出现在 JavaScript 代码的顶部或函数的顶部,可以帮助你写出更安全的 JavaScript 代码。如果你错误地创建了全局变量,它会通过抛出错误的方式来警告你。例如,以下程序将抛出错误:
function doSomething(val) {
"use strict";
x = val + 10;
}
复制代码它会抛出一个错误,因为 x 没有被定义,并使用了全局作用域中的某个值对其进行赋值,而 use strict 不允许这样做。下面的小改动修复了这个错误:
function doSomething(val) {
"use strict";
var x = val + 10;
}
13.JavaScript 中的 let 关键字有什么用?
除了可以在函数级别声明变量之外,ES6 还允许你使用 let 关键字在代码块({..})中声明变量。
14.什么是防抖和节流?有什么区别?如何实现?
防抖
触发高频事件后 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)); // 防抖
代码节流
高频事件触发,但在 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));
15.介绍下 Set、Map、WeakSet 和 WeakMap 的区别?
Set
成员唯一、无序且不重复;
[value, value],键值与键名是一致的(或者说只有键值,没有键名);
可以遍历,方法有:add、delete、has。
WeakSet
成员都是对象;
成员都是弱引用,可以被垃圾回收机制回收,可以用来保存 DOM 节点,不容易造成内存泄漏;
不能遍历,方法有 add、delete、has。
Map
本质上是键值对的集合,类似集合;
可以遍历,方法很多,可以跟各种数据格式转换。
WeakMap
只接受对象最为键名(null 除外),不接受其他类型的值作为键名;
键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的;
不能遍历,方法有 get、set、has、delete。
16.JS 异步解决方案
回调函数(callback)
setTimeout(() => {
// callback 函数体
}, 1000)
缺点:回调地狱,不能用 try catch 捕获错误,不能 return
回调地狱的根本问题在于:
缺乏顺序性:回调地狱导致的调试困难,和大脑的思维方式不符;
嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身,即(控制反转);
嵌套函数过多的多话,很难处理错误。
ajax('XXX1', () => {
// callback 函数体
ajax('XXX2', () => {
// callback 函数体
ajax('XXX3', () => {
// callback 函数体
})
})
})
Promise
Promise 就是为了解决 callback 的问题而产生的。
Promise 实现了链式调用,也就是说每次 then 后返回的都是一个全新 Promise,如果我们在 then 中 return ,return 的结果会被 Promise.resolve() 包装。
优点:解决了回调地狱的问题。
ajax('XXX1')
.then(res => {
// 操作逻辑
return ajax('XXX2')
}).then(res => {
// 操作逻辑
return ajax('XXX3')
}).then(res => {
// 操作逻辑
})
Generator
特点:可以控制函数的执行,可以配合 co.js 函数库使用。(也就是 koa 早期使用的库)
function \*fetch() {
yield ajax('XXX1', () => {})
yield ajax('XXX2', () => {})
yield ajax('XXX3', () => {})
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()
Async/await
async、await 是异步的终极解决方案。
优点是:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题;
缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。
async function test() {
// 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式
// 如果有依赖性的话,其实就是解决回调地狱的例子了
await fetch('XXX1')
await fetch('XXX2')
await fetch('XXX3')
}
下面来看一个使用 await 的例子:
let a = 0
let b = async () => {
a = a + await 10
console.log('2', a) // -> '2' 10
}
b()
a++
console.log('1', a) // -> '1' 1
上述解释中提到了 await 内部实现了 generator,其实 await 就是 generator 加上 Promise 的语法糖,且内部实现了自动执行 generator。如果你熟悉 co 的话,其实自己就可以实现这样的语法糖。
17.如何理解 JS 中的this关键字?
“this” 一般是表示当前所在的对象,但是事情并没有像它应该的那样发生。JS中的this关键字由函数的调用者决定,谁调用就this就指向哪个。如果找不到调用者,this将指向windows对象。
第一个例子很简单。调用 test对象中的 func(),因此func() 中的'this'指向的是 test 对象,所以打印的 prop 是 test 中的 prop,即 42。
var test = {
prop: 42,
func: function(){
return this.prop;
},
};
console.log (test.func()); // 42
如果我们直接调用getFullname函数,第二个例子将打印出'David Jones',因为此时 this 找不到调用者,所以默认就为 window 对象,打印的 fullname 即是全局的。
var fullname = ‘David Jones’
var obj ={
fullname: ‘Colin Brown’,
prop:{
fullname:’Aurelio Deftch’,
getFullname: function(){
return this.fullname;
}
}
}
var test = obj.prop.getFullname
console.log(test()) // David Jones
obj.prop.getFullname() // ‘Aurelio Deftch’
18.解释一下变量的提升
变量的提升是JavaScript的默认行为,这意味着将所有变量声明移动到当前作用域的顶部,并且可以在声明之前使用变量。初始化不会被提升,提升仅作用于变量的声明。
var x = 1
console.log(x + '——' + y) // 1——undefined
var y = 2
19.如何理解事件委托
这是一种让父元素上的事件监听器也影响子元素的技巧。通常,事件传播(捕获和冒泡)允许我们实现事件委托。冒泡意味着当触发子元素(目标)时,也可以逐层触发该子元素的父元素,直到它碰到DOM绑定的原始监听器(当前目标)。捕获属性将事件阶段转换为捕获阶段,让事件下移到元素; 因此,触发方向与冒泡阶段相反。捕获的默认值为false。
22.什么是事件传播?
当事件发生在DOM元素上时,该事件并不完全发生在那个元素上。在“冒泡阶段”中,事件冒泡或向上传播至父级,祖父母,祖父母或父级,直到到达window为止;而在“捕获阶段”中,事件从window开始向下触发元素 事件或event.target。
事件传播有三个阶段:
捕获阶段–事件从 window 开始,然后向下到每个元素,直到到达目标元素。
目标阶段–事件已达到目标元素。
冒泡阶段–事件从目标元素冒泡,然后上升到每个元素,直到到达 window。
21. event.preventDefault() 和 event.stopPropagation()方法之间有什么区别?
event.preventDefault() 方法可防止元素的默认行为。如果在表单元素中使用,它将阻止其提交。如果在锚元素中使用,它将阻止其导航。如果在上下文菜单中使用,它将阻止其显示或显示。event.stopPropagation()方法用于阻止捕获和冒泡阶段中当前事件的进一步传播。
22.原型继承是如何工作的
JavaScript中有一个超级对象,所有对象都将从中继承。'__ proto__'指向的对象的Prototype内部属性。原型(prototype )包含一个构造函数,使对象能够从中创建实例。__proto__始终存在于对象中,并且分层指向它所属的原型,直到null,这称为原型链。
23.对象的 prototype(原型) 是什么?
原型就是对象的蓝图。如果它存在当前对象中,则将其用作属性和方法的回退。它是在对象之间共享属性和功能的方法,这也是JavaScript实现继承的核心。
const o = {};console.log(o.toString()); // logs [object Object]
即使o对象中不存在o.toString方法,它也不会引发错误,而是返回字符串[object Object]。
当对象中不存在属性时,它将查看其原型,如果仍然不存在,则将其查找到原型的原型,依此类推,直到在原型链中找到具有相同属性的属性为止。
原型链的末尾是Object.prototype。
console.log(o.toString === Object.prototype.toString); // logs true
24.map, filter, reduce 各自有什么作用?
map 作用是生成一个新数组,遍历原数组,将每个元素拿出来做一些变换然后放入到新的数组中。
[1, 2, 3].map(v => v + 1) // -> [2, 3, 4]
另外 map 的回调函数接受三个参数,分别是当前索引元素,索引,原数组
['1','2','3'].map(parseInt)
第一轮遍历 parseInt('1', 0) -> 1
第二轮遍历 parseInt('2', 1) -> NaN
第三轮遍历 parseInt('3', 2) -> NaN
filter 的作用也是生成一个新数组,在遍历数组的时候将返回值为 true 的元素放入新数组,我们可以利用这个函数删除一些不需要的元素
let array = [1, 2, 4, 6]
let newArray = array.filter(item => item
!== 6)
console.log(newArray) // [1, 2, 4]
reduce对数组中的每个元素执行一个自定义的累计器,将其结果汇总为单个返回值
reduce的精华所在是将累计器逐个作用于数组成员上,把上一次输出的值作为下一次输入的值。
如果我们想实现一个功能将函数里的元素全部相加得到一个值,可能会这样写代码
const arr = [1, 2, 3]
let total = 0
for (let i = 0; i < arr.length; i++) {
total += arr[i]
}
console.log(total) //6
但是如果我们使用 reduce 的话就可以将遍历部分的代码优化为一行代码
const arr = [1, 2, 3]
const sum = arr.reduce((acc, current) =>
acc + current, 0)
console.log(sum)
❤️ 看完三件事
如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:
点赞,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓)。 关注公众号「入坑互联网」,不定期分享原创知识。 也看看其它文章
- END -
结伴同行前端路