其他分享
首页 > 其他分享> > 阿里前端一面必会面试题合集

阿里前端一面必会面试题合集

作者:互联网

什么是 DOM 和 BOM?

代码输出结果

var myObject = {
    foo: "bar",
    func: function() {
        var self = this;
        console.log(this.foo);  
        console.log(self.foo);  
        (function() {
            console.log(this.foo);  
            console.log(self.foo);  
        }());
    }
};
myObject.func();

输出结果:bar bar undefined bar

解析:

  1. 首先func是由myObject调用的,this指向myObject。又因为var self = this;所以self指向myObject。
  2. 这个立即执行匿名函数表达式是由window调用的,this指向window 。立即执行匿名函数的作用域处于myObject.func的作用域中,在这个作用域找不到self变量,沿着作用域链向上查找self变量,找到了指向 myObject对象的self。

代码输出结果

const p1 = new Promise((resolve) => {
  setTimeout(() => {
    resolve('resolve3');
    console.log('timer1')
  }, 0)
  resolve('resovle1');
  resolve('resolve2');
}).then(res => {
  console.log(res)  // resolve1
  setTimeout(() => {
    console.log(p1)
  }, 1000)
}).finally(res => {
  console.log('finally', res)
})

执行结果为如下:

resolve1
finally  undefined
timer1
Promise{<resolved>: undefined}

需要注意的是最后一个定时器打印出的p1其实是.finally的返回值,我们知道.finally的返回值如果在没有抛出错误的情况下默认会是上一个Promise的返回值,而这道题中.finally上一个Promise是.then(),但是这个.then()并没有返回值,所以p1打印出来的Promise的值会是undefined,如果在定时器的下面加上一个return 1,则值就会变成1。

代码输出结果

// a
function Foo () {
 getName = function () {
   console.log(1);
 }
 return this;
}
// b
Foo.getName = function () {
 console.log(2);
}
// c
Foo.prototype.getName = function () {
 console.log(3);
}
// d
var getName = function () {
 console.log(4);
}
// e
function getName () {
 console.log(5);
}

Foo.getName();           // 2
getName();               // 4
Foo().getName();         // 1
getName();               // 1 
new Foo.getName();       // 2
new Foo().getName();     // 3
new new Foo().getName(); // 3

输出结果:2 4 1 1 2 3 3

解析:

  1. Foo.getName(), Foo为一个函数对象,对象都可以有属性,b 处定义Foo的getName属性为函数,输出2;
  2. getName(), 这里看d、e处,d为函数表达式,e为函数声明,两者区别在于变量提升,函数声明的 5 会被后边函数表达式的 4 覆盖;
  3. ** Foo().getName(),** 这里要看a处,在Foo内部将全局的getName重新赋值为 console.log(1) 的函数,执行Foo()返回 this,这个this指向window,Foo().getName() 即为window.getName(),输出 1;
  4. getName(), 上面3中,全局的getName已经被重新赋值,所以这里依然输出 1;
  5. new Foo.getName(), 这里等价于 new (Foo.getName()),先执行 Foo.getName(),输出 2,然后new一个实例;
  6. new Foo().getName(), 这 里等价于 (new Foo()).getName(), 先new一个Foo的实例,再执行这个实例的getName方法,但是这个实例本身没有这个方法,所以去原型链__protot__上边找,实例.protot === Foo.prototype,所以输出 3;
  7. new new Foo().getName(), 这里等价于new (new Foo().getName()),如上述6,先输出 3,然后new 一个 new Foo().getName() 的实例。

选择排序--时间复杂度 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]));

代码输出结果

var friendName = 'World';
(function() {
  if (typeof friendName === 'undefined') {
    var friendName = 'Jack';
    console.log('Goodbye ' + friendName);
  } else {
    console.log('Hello ' + friendName);
  }
})();

输出结果:Goodbye Jack

我们知道,在 JavaScript中, Function 和 var 都会被提升(变量提升),所以上面的代码就相当于:

var name = 'World!';
(function () {
    var name;
    if (typeof name === 'undefined') {
        name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
    }
})();

这样,答案就一目了然了。

如何对项目中的图片进行优化?

  1. 不用图片。很多时候会使用到很多修饰类图片,其实这类修饰图片完全可以用 CSS 去代替。
  2. 对于移动端来说,屏幕宽度就那么点,完全没有必要去加载原图浪费带宽。一般图片都用 CDN 加载,可以计算出适配屏幕的宽度,然后去请求相应裁剪好的图片。
  3. 小图使用 base64 格式
  4. 将多个图标文件整合到一张图片中(雪碧图)
  5. 选择正确的图片格式:
    • 对于能够显示 WebP 格式的浏览器尽量使用 WebP 格式。因为 WebP 格式具有更好的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量,缺点就是兼容性并不好
    • 小图使用 PNG,其实对于大部分图标这类图片,完全可以使用 SVG 代替
    • 照片使用 JPEG

代码输出结果

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})

输出结果如下:

1
7
6
8
2
4
3
5
9
11
10
12

(1)第一轮事件循环流程分析如下:

宏任务Event Queue 微任务Event Queue
setTimeout1 process1
setTimeout2 then1

上表是第一轮事件循环宏任务结束时各Event Queue的情况,此时已经输出了1和7。发现了process1then1两个微任务:

第一轮事件循环正式结束,这一轮的结果是输出1,7,6,8。

(2)第二轮时间循环从**setTimeout1**宏任务开始:

宏任务Event Queue 微任务Event Queue
setTimeout2 process2
then2

第二轮事件循环宏任务结束,发现有process2then2两个微任务可以执行:

第二轮事件循环结束,第二轮输出2,4,3,5。

(3)第三轮事件循环开始,此时只剩setTimeout2了,执行。

宏任务Event Queue 微任务Event Queue
process3
then3

第三轮事件循环宏任务执行结束,执行两个微任务process3then3

第三轮事件循环结束,第三轮输出9,11,10,12。

整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。

NaN 是什么,用 typeof 会输出什么?

Not a Number,表示非数字,typeof NaN === 'number'

如何优化动画?

对于如何优化动画,我们知道,一般情况下,动画需要频繁的操作DOM,就就会导致页面的性能问题,我们可以将动画的position属性设置为absolute或者fixed,将动画脱离文档流,这样他的回流就不会影响到页面了。

回流与重绘的概念及触发条件

(1)回流

当渲染树中部分或者全部元素的尺寸、结构或者属性发生变化时,浏览器会重新渲染部分或者全部文档的过程就称为回流

下面这些操作会导致回流:

在触发回流(重排)的时候,由于浏览器渲染页面是基于流式布局的,所以当触发回流时,会导致周围的DOM元素重新排列,它的影响范围有两种:

(2)重绘

当页面中某些元素的样式发生变化,但是不会影响其在文档流中的位置时,浏览器就会对元素进行重新绘制,这个过程就是重绘

下面这些操作会导致回流:

注意: 当触发回流时,一定会触发重绘,但是重绘不一定会引发回流。

函数柯里化

什么叫函数柯里化?其实就是将使用多个参数的函数转换成一系列使用一个参数的函数的技术。还不懂?来举个例子。

function add(a, b, c) {
    return a + b + c
}
add(1, 2, 3)
let addCurry = curry(add)
addCurry(1)(2)(3)

现在就是要实现 curry 这个函数,使函数从一次调用传入多个参数变成多次调用每次传一个参数。

function curry(fn) {
    let judge = (...args) => {
        if (args.length == fn.length) return fn(...args)
        return (...arg) => judge(...args, ...arg)
    }
    return judge
}

代码输出结果

const first = () => (new Promise((resolve, reject) => {
    console.log(3);
    let p = new Promise((resolve, reject) => {
        console.log(7);
        setTimeout(() => {
            console.log(5);
            resolve(6);
            console.log(p)
        }, 0)
        resolve(1);
    });
    resolve(2);
    p.then((arg) => {
        console.log(arg);
    });
}));
first().then((arg) => {
    console.log(arg);
});
console.log(4);

输出结果如下:

3
7
4
1
2
5
Promise{<resolved>: 1}

代码的执行过程如下:

  1. 首先会进入Promise,打印出3,之后进入下面的Promise,打印出7;
  2. 遇到了定时器,将其加入宏任务队列;
  3. 执行Promise p中的resolve,状态变为resolved,返回值为1;
  4. 执行Promise first中的resolve,状态变为resolved,返回值为2;
  5. 遇到p.then,将其加入微任务队列,遇到first().then,将其加入任务队列;
  6. 执行外面的代码,打印出4;
  7. 这样第一轮宏任务就执行完了,开始执行微任务队列中的任务,先后打印出1和2;
  8. 这样微任务就执行完了,开始执行下一轮宏任务,宏任务队列中有一个定时器,执行它,打印出5,由于执行已经变为resolved状态,所以resolve(6)不会再执行;
  9. 最后console.log(p)打印出Promise{<resolved>: 1}

symbol 有什么用处

可以用来表示一个独一无二的变量防止命名冲突。但是面试官问还有吗?我没想出其他的用处就直接答我不知道了,还可以利用 symbol 不会被常规的方法(除了 Object.getOwnPropertySymbols 外)遍历到,所以可以用来模拟私有变量。

主要用来提供遍历接口,布置了 symbol.iterator 的对象才可以使用 for···of 循环,可以统一处理数据结构。调用之后回返回一个遍历器对象,包含有一个 next 方法,使用 next 方法后有两个返回值 value 和 done 分别表示函数当前执行位置的值和是否遍历完毕。

Symbol.for() 可以在全局访问 symbol

箭头函数和普通函数有啥区别?箭头函数能当构造函数吗?

function foo() {
  return (a) => {
    console.log(this.a);
  }
}

var obj1 = {
  a: 2
}

var obj2 = {
  a: 3 
}

var bar = foo.call(obj1);
bar.call(obj2);

代码输出问题

function fun(n, o) {
  console.log(o)
  return {
    fun: function(m){
      return fun(m, n);
    }
  };
}
var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3);
var b = fun(0).fun(1).fun(2).fun(3);
var c = fun(0).fun(1);  c.fun(2);  c.fun(3);

输出结果:

undefined  0  0  0
undefined  0  1  2
undefined  0  1  1

这是一道关于闭包的题目,对于fun方法,调用之后返回的是一个对象。我们知道,当调用函数的时候传入的实参比函数声明时指定的形参个数要少,剩下的形参都将设置为undefined值。所以 console.log(o); 会输出undefined。而a就是是fun(0)返回的那个对象。也就是说,函数fun中参数 n 的值是0,而返回的那个对象中,需要一个参数n,而这个对象的作用域中没有n,它就继续沿着作用域向上一级的作用域中寻找n,最后在函数fun中找到了n,n的值是0。了解了这一点,其他运算就很简单了,以此类推。

PWA使用过吗?serviceWorker的使用原理是啥?

渐进式网络应用(PWA)是谷歌在2015年底提出的概念。基本上算是web应用程序,但在外观和感觉上与原生app类似。支持PWA的网站可以提供脱机工作、推送通知和设备硬件访问等功能。

Service Worker是浏览器在后台独立于网页运行的脚本,它打开了通向不需要网页或用户交互的功能的大门。 现在,它们已包括如推送通知和后台同步等功能。 将来,Service Worker将会支持如定期同步或地理围栏等其他功能。 本教程讨论的核心功能是拦截和处理网络请求,包括通过程序来管理缓存中的响应。

10 个 Ajax 同时发起请求,全部返回展示结果,并且至多允许三次失败,说出设计思路

这个问题相信很多人会第一时间想到 Promise.all ,但是这个函数有一个局限在于如果失败一次就返回了,直接这样实现会有点问题,需要变通下。以下是两种实现思路

// 以下是不完整代码,着重于思路 非 Promise 写法
let successCount = 0
let errorCount = 0
let datas = []
ajax(url, (res) => {
     if (success) {
         success++
         if (success + errorCount === 10) {
             console.log(datas)
         } else {
             datas.push(res.data)
         }
     } else {
         errorCount++
         if (errorCount > 3) {
            // 失败次数大于3次就应该报错了
             throw Error('失败三次')
         }
     }
})
// Promise 写法
let errorCount = 0
let p = new Promise((resolve, reject) => {
    if (success) {
         resolve(res.data)
     } else {
         errorCount++
         if (errorCount > 3) {
            // 失败次数大于3次就应该报错了
            reject(error)
         } else {
             resolve(error)
         }
     }
})
Promise.all([p]).then(v => {
  console.log(v);
});

标签:function,面试题,console,log,getName,Promise,必会,new,合集
来源: https://www.cnblogs.com/loveX001/p/16683407.html