四道阿里前端面试基础题分析

题目一

小明使用js编写了一个dog类,该dog类有一个方法bark,在新建的dog实例中,引用bark方法,输出相关内容。
以下是小明写的代码,请看下他写的有什么问题?
如果有问题,如何改造?

function dog(){}
dog.prototype.bark = function(msg){
	console.log(msg);
	setTimeout(function(){
		this.bark(msg)
	},1000);
}
var d = new dog();
d.bark("wang");

分析:

这道题主要考察了javascript的原型链、闭包、this指向问题。

在这道题目中,创建了一个dog对象,在这个dog对象上用prototype追加了一个原型链上的方法bark。所以,当我们新建一个dog实例后,调用bark方法,首先会在该实例上查看有没有挂载这个bark方法,然后根据他原型链上的顺序去寻找bark方法。由于使用了prototype定义了一个方法,所以才可以在实例上调用bark方法。

bark方法的内部,this指向的是dog实例,但是当代码运行到setTimeout时,检测出定时器是一个异步函数,this的指向就会发生变化,指向了顶层对象(不同的运行环境顶层对象不一样)。由于在顶层对象中必定没有bark这个方法,所以会导致代码报错this.bark is not a function

对于这道题我们可以采用两种方法来解决,一种是用ES6语法中的剪头函数,调整setTimeoutthis的指向。还有一种方法就是利用闭包原理,将setTimeout函数外层创建一个that变量,赋值为当前的this,通过将this值分配给封闭的变量,可以解决this指向问题。

解答:

function dog() { }
dog.prototype.bark = function (msg) {
  // var that = this
  console.log(msg);
  setTimeout(function(){
    this.bark(msg);
  }, 1000);
}

var d = new dog();

d.bark("wang");

题目二

查找落单的数字,给定一个非空的数字数组,数组有且只有一个非重复项。
实例:

getSingleNumber([1, 2, 1, 2, 0]);		// 0
getSingleNumber([0, 0, 1, 0]);			// 1
getSingleNumber([1, 2, 3, 1, 2]);		// 3 

分析:

一道简单的算法题,注意题目中写有数组有且只有一个非重复项这是一个简化题目难度的关键点,我们只需要对比出一个数字与当前数组中的数字没有相同项,这个数字就是落单的数字。由于我们清晰的知道数组中只可能有一个落单数字,找到既可返回结束运算,所以我们可以使用some方法来遍历数组,当找到落单数字后直接return true结束运算。

解答:

function getSingleNumber(numbers) {
  let result = '';
  numbers.some((current, index) => {
    let flag = false;
    for (let i in numbers) {
      if (numbers[i] == current && i != index) {
        flag = true;
      }
    }
    if (flag == false) {
      result = numbers[index];
      return true;
    }
  })
  return result;
}

console.log(getSingleNumber([1, 2, 1, 2, 1, 3]));

题目三

使用Javascript编写函数sum,使得符合如下结果:sum(0.1, 0.2) === 0.3 // true

分析:

这道题牵涉一个Javascript很常见的运算问题,稍微多一点编程经验的人应该都遇到过,这就是经典的0.1 + 0.2 != 0.3问题。这是javascript中将数字转化为二进制的存放机制的问题,将0.1和0.2转化为二进制后是一串无限循环小数,即转化为二进制的时候就已经出现了精度丢失问题,再转化成十进制之后自然会出错,详细的原因不在过多阐述。

为了便捷解决这个问题,见到暴力的使用toFixed方法固定精度即可以达到效果,值得注意的是,toFixed方法的返回值是字符串类型,需要转化成Number才能与0.3完全相等。

解答:

function sum(a, b) {
  let result = a + b;
  return Number(result.toFixed(2));
}

console.log(sum(0.1, 0.2));
console.log(sum(0.1, 0.2) === 0.3);

题目四

简单实现一个事件订阅机制,具有监听on和触发emit方法,
示例:

const event = new EventEmitter();

event.on('someEvent', (...arr) => {
  console.log('some_event triggered', ...arr);
});

event.emit('someEvent', 'abc', '123', '444');

分析:

我们首先来搞懂何为事件订阅机制,注意这个订阅机制和监听什么的不是Javascript操作文档Dom的事件,而是设计模式中的观察者模式(也称订阅/发布机制,题目分析出也是这个意思)。

所谓的订阅机制,就是当调用一个实例对象的方法后,会有相应的监听机制去感知这个方法被调用,然后再执行一个方法,他们之间关联的只有时间名称,即为题中的someEvent

我们的基本思路为,创建一个EventEmitter对象(使用ES6中的class),在这个对象上挂载这两个方法,一个on监听器和一个emit触发器。同时在EventEmitterconstructor中定义一个handlers对象,用来存储监听器触发的方法,对象的键为监听方法的名字,值为这个方法被监听到后会执行的一系列方法(一个数组)。

当在代码中设置监听器(即为调用实例对象的on方法)时,先检测要监听的方法是否在handlers中有存储,如果没有存储,就说明这个方法时第一次被监听,在handlers对象中新建一条数据。如果这个方法不是第一次被设置监听,就把监听方法push入方法栈中。

当代码触发了一个方法(即为调用实例对象的emit方法)时,先检查handlers有没有存放方法的名字,如果有,即说明该方法被监听,直接调用存储handlers对象中存储的函数方法,这样就达成了一次监听的触发。

解答:

class EventEmitter {
  constructor() {
    this.handlers = {}
  };
  on(eventName, handler) {
    if (!(eventName in this.handlers)) {
      this.handlers[eventName] = [];
    }
    this.handlers[eventName].push(handler);
  };
  emit(eventName) {
    let arr = [...arguments];
    arr.shift();
    for (let i = 0; i < this.handlers[eventName].length; i++) {
      this.handlers[eventName][i](...arr);
    }
  }
}

const event = new EventEmitter();

event.on('someEvent', (...arr) => {
  console.log('some_event triggered', ...arr);
});

event.emit('someEvent', 'abc', '123', '444');

结果:some_event triggered abc 123 444

发布了48 篇原创文章 · 获赞 28 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/u012925833/article/details/89001412