小明使用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语法中的剪头函数,调整setTimeout
中this
的指向。还有一种方法就是利用闭包原理,将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
触发器。同时在EventEmitter
的constructor
中定义一个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