JS常见面试题及答案(贴近实战版)
1.说下原型和原型链,实际开发中哪里用到?
答案:
原型是JS对象的“模板”,每个对象(除null)都有__proto__(隐式原型),指向其构造函数的prototype(显式原型)。原型链就是对象通过__proto__逐层向上查找属性的链条,直到Object.prototype.__proto__(值为null)。
实际用例:
实现继承:比如封装组件时,让自定义弹窗构造函数的prototype指向Object.create(基础弹窗.prototype),复用基础弹窗的“关闭”“显示”方法;
共享方法:数组的map、filter方法都存在Array.prototype上,所有数组实例通过原型链调用,避免每个数组都复制一份方法,节省内存。
注意:不要直接修改__proto__,性能差且不推荐,推荐用Object.create或class(ES6语法糖,本质还是原型)。
2.闭包是什么?用它的时候要注意什么?
答案:
闭包就是“函数嵌套函数,内层函数引用外层函数的变量,且内层函数被外部访问”,这时外层函数的变量不会被GC(垃圾回收)回收,能一直被内层函数使用。
实际例子:
写防抖函数时用闭包存定时器ID,避免每次调用防抖函数都重新创建定时器:
functiondebounce(fn,delay){
lettimer=null;//闭包保存timer,不会被回收
returnfunction(){
clearTimeout(timer);
timer=setTimeout(()=fn.apply(this,arguments),delay);
};
}
constinputDebounce=debounce(handleInput,300);
注意点:
内存泄漏:如果闭包引用的变量是大对象(比如DOM元素),且闭包一直存在(比如没清除的定时器、全局变量引用),会导致内存占着不放,要及时销毁(比如清除定时器、解除全局引用);
不要过度使用:比如循环里创建闭包(早期var变量提升问题),现在用let块级作用域能避免,但仍需注意没必要的闭包会增加内存开销。
3.this指向怎么判断?举几个常见场景
答案:
this指向“调用函数的对象”,不是定义时的对象,核心看“谁调用”,分5种常见场景:
全局调用(非严格模式):this指向window(浏览器)/global(Node),严格模式下是undefined;
例:functionfn(){console.log(this)}fn()→非严格模式输出window。
对象方法调用:this指向调用方法的对象;
例:constobj={name:a,say(){console.log(this.name)}}obj.say()→输出a。
call/apply/bind:this指向第一个参数(传null/undefined时,非严格模式指向window);
例:obj.say.call({name:b})→输出b。
构造函数(new):this指向新创建的实例;
例:functionPerson(name){this.name=name}constp=newPerson(c)→p.name是c。
箭头函数:没有自己的this,继承外层“非箭头函数”的this(跟定义位置有关,不是调用位置);
例:constobj={fn:()={console.log(this)}}obj.fn()→输出window(因为fn是箭头函数,继承外层全局的this)。
实际坑点:Vue组件methods里的this指向组件实例,就是因为Vue内部用call把this绑定到了实例上。
4.0.1+0.2为什么不等于0.3?怎么解决?
答案:
因为JS用“二进制浮点数”存储数字,而0.1和0.2转成二进制后是无限循环的,浏览器会截取前53位(JS能精确表示的最大位数),导致精度丢失,所以相加后是0.30000000000000004,不是0.3。
解决方法(实际开发常用):
转成整数计算:比如涉及金额时,把元转成分(乘以100),计算后再转回去;
例:(0.1*10+0.2*10)/10=0.3。
用toFixed(注意四舍五入坑):需要先判断小数位数,比如(0.1+0.2).toFixed(1)→输出0.3(但1.335.toFi