一.说一说JS实现异步的方法?
有5种方法实现异步:回调函数、事件监听、发布/订阅、Promise、生成器Generators/yield。
1.回调函数
优点:回调函数的优点是简单、容易理解和实现。
缺点:缺点是不利于代码的阅读和维护,各个部分之间高度耦合,使得程序结构混乱、流程难以追踪(尤其是多个回调函数嵌套的情况),而且每个任务只能指定一个回调函数。
function f1(callback) {
setTimeout(function () {
callback();
},1000);
}
f1(f2);
2.Promise
Promise包装了一个异步调用并生成一个Promise实例,当异步调用返回的时候根据调用的结果分别调用实例化时传入的resolve 和 reject方法,then接收到对应的数据,做出相应的处理。
优点:Promise不仅能够捕获错误,回调函数变成了链式写法,程序的流程可以看得很清楚,而且也很好地解决了回调地狱的问题。
缺点:缺点是无法取消 Promise,错误需要通过回调函数捕获。
f1().then(f2);
function f1(){
//deferred对象就是jQuery的回调函数解决方案。
var dfd = $.Deferred();
setTimeout(function () {
// f1的任务代码
//将dtd对象的执行状态从"未完成"改为"已完成",从而触发done()方法
dfd.resolve();
}, 500);
//返回promise对象
// deferred.promise()方法。它的作用是,在原来的deferred对象上返回另一个deferred对象,
//后者只开放与改变执行状态无关的方法(比如done()方法和fail()方法),
//屏蔽与改变执行状态有关的方法(比如resolve()方法和reject()方法),
//从而使得执行状态不能被改变。
return dfd.promise;
}
f1().then(f2).then(f3); //指定多个回调函数
f1().then(f2).fail(f3); //指定发生错误时的回调函数
3.Generator
Generator 函数是 ES6 提供的一种异步编程解决方案,Generator 函数是一个状态机,封装了多个内部状态,可暂停函数, yield可暂停,next方法可启动,每次返回的是yield后的表达式结果。
优点:优点是异步语义清晰。
缺点:缺点是手动迭代`Generator` 函数很麻烦,实现逻辑有点绕 。
function *generatorDemo() {
yield 'hello';
yield 1 + 2;
return 'ok';
}
var demo = generatorDemo()
demo.next() // { value: 'hello', done: false }
demo.next() // { value: 3, done: false }
demo.next() // { value: 'ok', done: ture }
demo.next() // { value: undefined, done: ture }
4.事件监听
思路是采用事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。
优点:比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以"去耦合"(Decoupling),有利于实现模块化。
缺点:缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。
f1.on('done', f2);
function f1(){
setTimeout(function () {
// f1的任务代码
f1.trigger('done');
}, 1000);
}
5.发布/订阅
发布/订阅模式,又称观察者模式。发布/订阅,性质与“事件监听类似”,但是明显优于后者,因为我们可以通过查看”消息中心“,了解存在多少信号,多少个订阅者,从而监听程序的运行。
//f2向信号中心Jquery订阅done信号
jQuery.subscribe("done", f2);
function f1(){
setTimeout(function () {
// f1的任务代码
//发布done信号
jQuery.publish("done");
}, 1000);
}
//f2执行完成后,取消订阅
jQuery.unsubscribe("done", f2);
二.说一说数组去重都有哪些方法?
数组去重有这些方法:对象属性、new Set() 、indexOf、hasOwnProperty、reduce+includes、filter。
1.new Set()
利用Set类型数据无重复项:new 一个 Set,参数为需要去重的数组,Set 会自动删除重复的元素,再将 Set 转为数组返回。
优点:效率更高,代码简单,思路清晰。
缺点:可能会有兼容性问题。
var arr = [1, 2, 3,4 ,5,6, 4, 3, 8, 1]
// 数组去重:
// 方法4: set
function newArrFn (arr) {
// .new Set方法,返回是一个类数组,需要结合 ...运算符,转成真实数组
return ([...new Set(arr)])
}
console.log(newArrFn(arr));
2.filter+indexof 去重
这个方法和第一种方法类似,利用 Array 自带的 filter 方法,返回 arr.indexOf(num) 等于 index 的num。原理就是 indexOf 会返回最先找到的数字的索引,假设数组是 [1, 1],在对第二个1使用 indexOf 方法时,返回的是第一个1的索引0。
优点:可以在去重的时候插入对元素的操作,可拓展性强。
var arr = [1, 2, 3,4 ,5,6, 4, 3, 8, 1]
// 数组去重:
// 方法6 :filter + findIndex
function newArrFn (arr) {
// 利用indexOf检测元素在数组中第一次出现的位置是否和元素现在的位置相等,
// 如果相等,说明数组中没有重复的
return Array.prototype.filter.call(arr, function (item, index) {
return arr.indexOf(item) === index
})
}
console.log(newArrFn(arr));
var arr = [1, 2, 3,4 ,5,6, 4, 3, 8, 1]
// 数组去重:
// 方法6 :filter + findIndex
function newArrFn (arr) {
// 利用indexOf检测元素在数组中第一次出现的位置是否和元素现在的位置相等,
// 如果相等,说明数组中没有重复的
return arr.filter(function (item, index) {
return arr.indexOf(item) === index
})
}
console.log(newArrFn(arr));
3.for + indexof
主要利用indexof的特性,查找元素找不到
就返回-1
, 接下来就需要判断,如果是-1,说明没找到,就往新数组里面添加元素。
var arr = [1, 2, 3,4 ,5,6, 4, 3, 8, 1]
// 数组去重:
// 方法2: for + indexof
function newArrFn (arr) {
let newArr = []
for(let i = 0;i<arr.length;i++){
newArr.indexOf(arr[i]) === -1 ? newArr.push(arr[i]) : newArr
};
return newArr
}
console.log(newArrFn(arr));
4.reduce +includes
这个方法就是利用reduce遍历和传入一个空数组作为去重后的新数组,然后内部判断新数组中是否存在当前遍历的元素,不存在就插入到新数组中。
缺点:时间消耗多,内存空间也有额外占用。
var arr = [1, 2, 3,4 ,5,6, 4, 3, 8, 1]
// 数组去重:
// 方法12 :reduce
function newArrFn (arr) {
let newArr = []
return arr.reduce((prev, next,index, arr) => {
// 如果包含,就返回原数据,不包含,就把新数据追加进去
return newArr.includes(next) ? newArr : newArr.push(next)
}, 0)
}
console.log(newArrFn(arr));
var arr = [1, 2, 3,4 ,5,6, 4, 3, 8, 1]
// 数组去重:
// 方法12 :reduce
function newArrFn (arr) {
return arr.reduce((prev, next,index, arr) => {
// 如果包含,就返回原数据,不包含,就把新数据追加进去
prev.includes(next) ? prev : prev.push(next)
return prev
}, [])
}
console.log(newArrFn(arr));
5.sort 排序
首先利用 sort 方法
进行排序。进行循环,如果原数组的第 i 项
和新数组的 i - 1
项不一致,就push
进去。
var arr = [1, 2, 3,4 ,5,6, 4, 3, 8, 1]
// 数组去重:
// 方法3: for + sort
function newArrFn (arr) {
arr = arr.sort()
let newArr = []
for(let i = 0;i<arr.length;i++){
arr[i] === arr[i-1] ? newArr : newArr.push(arr[i])
};
return newArr
}
console.log(newArrFn(arr));
三.说一说es6中箭头函数?
箭头函数没有this、this是从外部获取、不能使用new、没有arguments、没有原型和super ,箭头函数相当于匿名函数,简化了函数定义。
由于没有this关键字所以箭头函数也不能作为构造函数,不能使用yield关键字,因此箭头函数不能用作 Generator 函数。
箭头函数函数适用场景: -简单的函数表达式,内部没有this引用,没有递归、事件绑定、解绑定,适用于map、filter等方法中。
四.说一说call apply bind的作用和区别?
相同点:call、apply、bind的作用都是改变函数运行时的this指向。
不同点:
1.bind在改变this指向的时候,返回一个改变执行上下文的函数,不会立即执行函数,但是call和apply在改变this指向的同时执行了该函数。
2.但是call和apply参数的格式不同,call是一个参数对应一个原函数的参数,但是apply第二个参数是数组,
call 的使用场景:
1、对象的继承。如下面这个例子:
function Animal(name) {
this.name = name;
this.showName = function () {
console.log(this.name);
}
}
function Cat(name) {
Animal.call(this, name);
}
var cat = new Cat('Black Cat');
cat.showName();
var animal = new Animal('父元素')
animal.showName()
apply 的使用场景:
1、Math.max。用它来获取数组中最大最小的一项。
let max = Math.max.apply(null, array);
let min = Math.min.apply(null, array);
2、实现两个数组合并。在 ES6 的扩展运算符出现之前,我们可以用 Array.prototype.push来实现。
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
Array.prototype.push.apply(arr1, arr2);
console.log(arr1); // [1, 2, 3, 4, 5, 6]
五.js数组面试题
// 面试题一
// 已知有字符串foo = 'get-element-by-id',写一个函数将其转化为驼峰表示法"getElementById"
function HumpConversion() {
let foo = "get-element-by-id"
let arr = foo.split('-')
for (let i = 1; i < arr.length; i++) {
arr[i] = arr[i].charAt(0).toLocaleUpperCase() + arr[i].substring(1, arr[i].length)
}
arr = arr.join('')
return arr
}
let newArr = HumpConversion()
// console.log(newArr)
// 面试题二
// 已知数组,arr = [5,2,6,8,4,1,3,7,0,9]
// 请自定义一个原生函数,使数组从小到大排序,比如ArrSort
function ArrSort(arr) {
// let arr = [5,2,6,8,4,1,3,7,0,9]
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
var temp = arr[j]
arr[j] = arr[j + 1]
arr[j + 1] = temp
}
}
}
return arr
// console.log(arr);
}
ArrSort([5, 2, 6, 8, 4, 1, 3, 7, 0, 9])
// 如果数组是这样的呢?arr = ['-5,3,8,2','1,6,9,4','10,5,-3,7','11,-7,12,13']
// 请自定义一个原生函数,使数组从小到大排序,比如BubbleSort
let arr = ['-5,3,8,2', '1,6,9,4', '10,5,-3,7', '11,-7,12,13']
//把数组转化成数值型数组
function Concat(array) {
let newArr = []
for (let i = 0; i < array.length; i++) {
newArr = newArr.concat(array[i].split(','))
}
for (let j = 0; j < newArr.length; j++) {
newArr[j] = parseInt(newArr[j])
}
// console.log(newArr)
return newArr
}
async function BubbleSort() {
let sortArr = await Concat(arr)
return ArrSort(sortArr)
}
BubbleSort().then(res => {
// console.log(res);
})
// 面试题三
// 已知数组 arr = [1,2,3,4,5,6,7,8,9,10]
// 请自定义函数反转数组
function reverArr(arr) {
for (let i = 0; i < arr.length / 2; i++) {
var temp = arr[i]
arr[i] = arr[arr.length - 1 - i]
arr[arr.length - 1 - i] = temp
}
// console.log(arr)
}
reverArr([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
// 面试题四
// 已知数组 arr = [8,11,20,5,20,8,0,2,4,0,8]
// 去掉数组重复的数据
// 方法一
function removal(arr) {
let newArr = []
for (let i = 0; i < arr.length; i++) {
if (newArr.indexOf(arr[i]) < 0){
newArr.push(arr[i])
}
}
// console.log(newArr)
}
removal([8, 11, 20, 5, 20, 8, 0, 2, 4, 0, 8])
// 方法二
function removal2(arr){
let newArr = []
for(let i=0;i<arr.length;i++){
if(!newArr.includes(arr[i])){
newArr.push(arr[i])
}
}
// console.log(newArr);
}
removal2([8, 11, 20, 5, 20, 8, 0, 2, 4, 0, 8])
// 方法三
function removal3(arr){
let newArr = [...new Set(arr)]
// console.log(newArr);
}
removal3([8, 11, 20, 5, 20, 8, 0, 2, 4, 0, 8])
// 方法四
function removal4(arr){
let newArr = []
newArr[0] = arr[0]
for(let i=0;i<arr.length;i++){
for(let j=0;j<newArr.length;j++){
if(newArr[j] === arr[i] ){
break
}
if(j === newArr.length-1){
newArr.push(arr[i])
}
}
}
// console.log(newArr)
}
removal4([8, 11, 20, 5, 20, 8, 0, 2, 4, 0, 8])
六.1物理像素实现
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>1物理像素实现</title>
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no"/>
<style>
*{
margin: 0;
padding: 0;
}
#box{
width: 0.5rem;
height: 0.5rem;
border-bottom: 1px solid #000;
}
</style>
</head>
<body>
<div id="box"></div>
</body>
<script>
// 像素比 = 物理像素 / css像素
//像素比
let dpr = window.devicePixelRatio
// 缩放比例/css像素
let scale = 1 / dpr
// 获取屏幕宽度
let width = document.documentElement.clientWidth
// 获取mate标签
let meta = document.querySelector("meta[name='viewport']")
meta.setAttribute('content',`width=device-width,initial-scale=${scale},user-scalable=no`)
// 获取html标签
let htmlNode = document.querySelector('html')
htmlNode.style.fontSize = width * dpr + 'px'
</script>
</html>
七.实现移动端rem适配
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no"/>
<title>实现移动端rem适配</title>
<style>
*{
margin: 0;
padding: 0;
}
.box{
width: 0.5rem;
height: 0.5rem;
background-color: red;
font-size: 0.05rem;
color: #fff;
text-align: center;
line-height: 0.5rem;
}
</style>
</head>
<body>
<div class="box">实现移动端rem适配</div>
</body>
<script>
window.onload = function(){
let width = document.documentElement.clientWidth;
let htmlNode = document.querySelector('html');
htmlNode.style.fontSize = width + 'px'
}
</script>
</html>
八.什么是闭包?
闭包是指在一个函数内部定义的函数,它可以访问外部函数的变量和参数,即使外部函数已经返回。闭包可以用来创建私有变量和方法,以及实现模块化和高阶函数等功能。以下是一个使用闭包实现私有变量和方法的示例:
function createCounter() { let count = 0; return { increment() { count++; }, decrement() { count--; }, getCount() { return count; } }; } const counter = createCounter(); console.log(counter.getCount()); // 0 counter.increment(); counter.increment(); console.log(counter.getCount()); // 2 counter.decrement(); console.log(counter.getCount()); // 1
在这个示例中,createCounter函数返回一个包含三个方法的对象。这些方法可以访问createCounter函数中定义的count变量,但是外部代码无法直接访问count变量。这样,我们就创建了一个私有变量和方法,可以避免全局命名冲突和数据污染的问题。