前端面试题
Call、Apply和 Bind 的区别?
- Call、Apply、bind 都可以改变函数内部的 this
call
-
call()
方法调用一个函数,其具有一个指定的this
值和分别地提供的参数(参数的列表) -
注意:该方法的作用和
apply()
方法类似,只有一个区别,就是call()
方法接收的是若干个参数的列表,而apply()
方法接收的是一个包含多个参数的数组。 -
语法:
fun.call(thisArg,arg1,arg2)
-
参数:
thisArg
- 在 fun 函数运行时指定的 this 值
- 如果指定了 null 和 undefined 则内部 this 指向 window
-
arg1,arg2,...
- 指定的参数列表
<script> var obj = { name: "admin", }; function foo(a, b) { console.log(this.name); // admin this指向obj console.log(a + b); // 30 } foo.call(obj, 10, 20); </script>
apply
-
apply()
方法调用一个函数,其具有一个指定的this
值,以及作为一个数组(或类似数组的对象)提供的参数 -
注意:该方法的作用和
call()
方法类似,只有一个区别,就是call()
方法接受的是若干个参数的列表,而apply()
方法接收的是一个包含多个参数的数组。 -
语法:
fun.apply(thisArg,[argsArray])
-
参数:
thisArg
argsArray
-
apply()
与call()
非常相似,不同之处在于提供参数的方式 -
apply()
使用参数数组而不是一组参数列表- 例如:
fun.apply(this,['eat','bananas'])
<script> var obj = { name: "admin", }; function foo(a, b) { console.log(this.name); // admin this指向obj console.log(a + b); // 30 } foo.apply(obj, [10, 20]); </script>
- 例如:
bind
-
bind()
函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体。 -
当目标函数被调用时
this
值绑定到bind()
的第一个参数,该函数不能被重写。绑定函数被调用时,bind()
也接收预设的参数提供给原函数 -
一个绑定函数也能使用 new 操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数
-
语法:
fun.bind(thisArg,arg1,arg2,...)
-
参数:
thisArg
- 当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用 new 操作符调用绑定函数时,该参数无效
arg1,arg2,...
- 当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法
<script> var obj = { name: "admin", }; function foo(a, b, c, d) { console.log(this.name); // admin this指向obj console.log(a + b); // 30 console.log(c + d); // 70 } // foo.bind(obj, 10, 20)(); // 最后一个括号表示调用 foo.bind(obj, 10, 20)(30, 40); </script>
小结
call()
和apply()
特性一样- 都是用来调用函数,而且是立即调用
- 但是可以在调用函数的同时,通过第一个参数指定函数内部
this
的指向 - call 调用的时候,参数必须以参数列表的形式进行传递,也就是以逗号分割的方式依次传递即可
- apply 调用的时候,参数必须是一个数组,然后在执行的时候,会将数组内部的元素一个一个拿出来,与形参一一对应进行传递
- 如果第一个参数指定了
null
或者undefined
,则内部 this 指向 window
bind
- 可以用来指定内部 this 的指向,然后生成一个改变了 this 指向的新的函数
- 它和
call
、apply
最大的区别是:bind
不会调用 bind
支持传递参数,它的传参方式比较特殊,一共有两个位置可以传递- 在
bind
的同时,以参数列表的形式进行传递 - 在调用的时候,以参数列表的形式进行传递
- 在
bind
的时候以传递的参数为准还是以调用的时候传递的参数为准- 两者合并:
bind
的时候传递的参数和调用的时候传递的参数会合并到一起。传递到函数内部
- 两者合并:
JS 延迟加载方式有哪些?
-
js 的加载、解析和执行会阻塞页面的渲染过程,因此我们希望 js 脚本能够尽可能的延迟加载,提高页面的渲染速度。
-
JS 延迟加载的方式:
-
将 js 脚本放在文档底部,来使 js 脚本尽可能的在最后来执行
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body></body> <script src="./js1.js"></script> </html>
-
给 js 脚本添加 defer 属性,这个属性会让脚本的加载与文档的解析同步,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样的
- 表面脚本在执行时不会影响页面的构造,也就是说,脚本会被延迟到整个页面都解析完毕之后再执行。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <!-- 浏览器会立即下载 但延迟执行 --> <script src="./js1.js" defer></script> </head> <body></body> </html>
-
给 js 脚本添加 async 属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js 脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async 属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行
- 不让页面等待脚本下载和执行,从而异步加载页面其他内容
- 缺点:不能控制加载的顺序
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="./js1.js" async></script> </head> <body></body> </html>
-
动态创建 DOM 标签的方式,我们可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本
<script type="text/javascript"> function downloadJS() { var element = document.createElement("script"); element.src = "test.js"; document.body.appendChild(element); } //三种事件模型,DOM0,DOM2,IE if (window.addEventListener) { window.addEventListener("load", downloadJS, false); } else if (window.attachEvent) { window.attachEvent("onload", downloadJS); } else { window.onload = downloadJS; } </script>
-
盒子模型box-sizing
有几种,都有什么区别?
-
W3C 盒子模型(
content-box
)-
box-width = width + 2 * padding + 2 * border
-
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <style> div { box-sizing: content-box; width: 100px; height: 100px; padding: 1px; border: 1px solid yellow; background: red; } </style> </head> <body> <div></div> </body> </html>
-
-
IE 盒子模型(
border-box
)-
box-width = width
-
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <style> div { box-sizing: border-box; width: 100px; height: 100px; padding: 1px; border: 1px solid yellow; background: red; } </style> </head> <body> <div></div> </body> </html>
-
第一行和第二行的颜色分别是什么?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
.red {
color: red;
}
.green {
color: green;
}
</style>
</head>
<body>
<div class="red green">第一行:颜色是什么?</div>
<div class="green red">第二行:颜色是什么?</div>
</body>
</html>
- 答:
- 两行都为绿色
- 因为后声明的样式优先级高
简述防抖和节流
-
函数防抖:是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求
- 防抖:就是将一段时间内连续的多次触发转化为最后一次触发
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <input type="text" /> </body> <script> // 防抖:用户触发事件过于频繁,只要最后一个事件的操作 let input = document.querySelector("input"); // let t = null; // input.onchange = function () { // if (t !== null) { // clearTimeout(t); // } // t = setTimeout(() => { // console.log(this.value); // }, 500); // }; // 根据上面的代码做出封装 使代码逻辑便于维护 input.onchange = debounce(function () { console.log(this.value); }, 500); // 利用闭包 function debounce(fn, delay) { let t = null; return function () { if (t !== null) { clearTimeout(t); } t = setTimeout(() => { fn.call(this); }, delay); }; } </script> </html>
-
函数节流:是指规定一个单位事件,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。
- 节流:减少一段时间内触发的频率
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <style> body { height: 2000px; } </style> </head> <body></body> <script> // 防抖:只执行最后一次 // 节流:控制执行次数 作用:控制高频时间执行次数 // let flag = true; // window.onscroll = function () { // if (flag) { // setTimeout(() => { // console.log("hello world"); // flag = true; // }, 500); // flag = false; // } // }; window.onscroll = throttle(function () { console.log("hello world"); }, 500); function throttle(fn, delay) { let flag = true; return function () { if (flag) { setTimeout(() => { fn.call(this); flag = true; }, delay); flag = false; } }; } </script> </html>
-
区别:两者区别在于函数节流是固定时间做某一件事,比如每隔 1 秒发一次请求。而函数防抖是频繁触发后,只执行一次(两者的前提都是频繁触发)
以下console.log
打印顺序的判断
<script>
setTimeout(function () {
console.log("1");
}, 0);
new Promise(function (resolve) {
console.log("2");
resolve();
}).then(function () {
console.log("3");
});
console.log("4");
// 2 4 3 1
// 打印顺序: Promise log Promise其后的.then() setTimeout
</script>