32个常见的 JavaScript 面试手写代码问题,帮你巩固基础
1.模拟call和apply/ bind原理
- call和apply的区别是什么?哪个性能更好一些?
- call和apply都是Function原型上的方法,每一个函数作为Function的实例都可以调用这两个方法,call和apply执行的目的都是用来让函数执行并且改变this指向
- 唯一的区别是call传参要求一个个传递,apply要以数组的形式传参
- 跟call/apply一样改变this的方法还有bind,只是bind并没有将函数立即执行,仅仅改变this指向
- call的性能要比apply好那么一丢丢,尤其是传递给函数的参数超过三个的时候
function fun1() {
console.log(this, 1);
}
fun1.call.call.call("hello");
Function.prototype.myCall = function(context){
const ctx = context? Object(context) : window;
console.log(this);
ctx.fn = this;
const args = [...arguments].slice(1);
const result = ctx.fn(...args);
delete ctx.fn;
return result;
}
Function.prototype.myApply = function(context) {
const ctx = context? Object(context) : window;
ctx.fn = this;
const args = arguments[1];
let result;
if(args) {
result = ctx.fn(...args);
}else{
result = ctx.fn();
}
delete ctx.fn;
return result;
}
- bind方法的实现
- 返回一个绑定后的函数; 函数柯理化 bind之后返回的函数仍可以继续传参
Function.prototype.myBind = function(context){
const self = this;
const firstArg = Array.prototype.slice(arguments, 1);
return function(){
let secondArgs = Array.prototype.slice(arguments);
let finalArgs = firstArgs.concat(secondArgs);
return self.apply(context, finalArgs);
}
}
2.模拟new的实现
function Animal(type) {
this.type = type;
}
Animal.prototype.say = function () {
console.log("say");
};
let animal = new Animal("monkey");
let animal1 = myNew(Animal, "monkey");
function myNew() {
const constru = [].shift.call(arguments);
const obj = Object.create(constru.prototype);
const result = constru.apply(obj, arguments);
return result instanceof Object ? result : obj;
}
function myNew1() {
const constru = [].shift.call(arguments);
let obj = {};
obj.__proto__ = constru.prototype;
let r = constru.apply(obj, arguments);
return r instanceof Object ? r : obj;
}
3. 为什么 0.1 + 0.2 != 0.3
- 将数转换成二进制的时候出现了偏差,计算的时候 会比以前大一些的值,加出来的值也就比0.3大一点
在计算机存储中,所有的数据都会存储成二进制,包括上面的运算时也会先把这些数据转换成二进制再进行运算
进制转换的规则:
0.1 这样的十进制数要转换成二进制:
要知道整数小数分别是怎么转换的
整数部分 0
小数部分 1
二进制转十进制: 如: 1010 =》 10 (1*2^3 + 0*2^2 + 1*2^1 + 0*2^0 = 10)
js中可以后方法实现将二进制数转换成十进制: console.log(parseInt(1010, 2));
二进制数:11.0101 =》 1*2^1 + 0*2^0 + 0*2^(-1) + 1*2^(-2) + 0*2^(-3) + 1*2^(-4)
十进制转二进制:
整数部分: 取余法
小数部分: 把当前的不停乘2取整
0.1 转化成二进制 0.00011001100110011.......
0.1 * 2 = 0.2 无整数
0.2 * 2 = 0.4 无整数
0.4 * 2 = 0.8 无整数
0.8 * 2 = 1.6 上1余0.6
0.6 * 2 = 1.2 上1余0.2 循环。。。
console.log(0.1.toString(2)); "0.0001100110011001100110011001100110011001100110011001101"
4.typeof 和 instanceof的区别
都可以校验数据类型
typeof
1. 对于原始数据类型
校验原始数据类型 6种原始类型: number/string/boolean/undefined/null/Symbol
特殊的: typeof(null) === "object"
2. 对于引用数据类型
如果是函数 typeof(function(){}) === "function"
其他任意引用类型 返回 "object"
所以并不能准确的判断引用数据的类型
所以有了通过Object.prototype.toString.call()进行校验
Object.prototype.toString.call([]) "[object Array]"
Object.prototype.toString.call(new RegExp("/A/")) "[object RegExp]"
Object.prototype.toString.call(function(){}) "[object Function]"
这个方法的缺点是: 只能校验当前已经存在的类型
如
class A{}
let a = new A()
Object.prototype.toString.call(a) => "[object Object]"
于是有了instanceof
[] instanceof Array => true
[].__proto__ === Array.prototype
[] instanceof Object => true
[].__proto__.__proto__ === Array.prototype
即A instanceof B 左边有很多个__proto__,右边为 B.prototype
实现instanceof 如下
缺点: 无法校验原始类型, 只能校验A是不是B的实例
5. 面试笔试题(事件委托/发布订阅/判断回文/去掉指定的斜杠)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="container">
<div id="itemx" class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
</div>
<script>
const containerBox = document.getElementById("container");
document.addEventListener("click", function(e) {
const target = e.target;
if(target.nodeName.toLowerCase() === 'div' && target.id !== 'itemx') {
console.log(e.target.innerHTML)
}
})
class EventEmitter{
constructor(){
this.events = {};
}
public addEventListener(eventName: string, handler: Function){
if(this.events[eventName]){
this.events[eventName].push(handler);
}else {
this.events[eventName] = [handler];
}
}
public removeEventListener(eventName: string, handler: Function){
if(this.events && this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter(fn => fn !== handler)
}
}
public dispatch(eventName: string, params: any){
this.events[eventName].forEach(fn => fn(params))
}
}
function isPlaindrome(str){
let reverseStr = "";
for(let i = str.length - 1; i >=0; i--) {
reverseStr += str[i];
}
return str === reverseStr;
}
function isPlaindrome(str){
for(let i = 0; i < str.length; i++) {
if(str.charAt(i) !== str.charAt(str.length -1 - i)) {
return false;
}
}
return true;
}
function removeSlashByNum(num) {
let str = "abc/defg/hi/jkl/mn/opqrst";
let newStr = "";
let indexNum = 0;
let index = str.indexOf("/");
if(num === 0) {
newStr = str;
}
while(index >= 0) {
indexNum ++;
if(num === indexNum) {
newStr = str.substring(0,index) + str.substring(index+1);
}
index = str.indexOf("/", index + 1);
}
}
</script>
</body>
</html>
6. 扁平化数据树状结构化
7. 菜鸟面试题
function add(){
const arg = [...arguments];
function a(){
arg.push(...arguments);
return a;
}
a.toString = function(){
return arg.reduce((pre, cur) =>{
return pre + cur;
})
}
return a;
}
const arr = [10, [ 21, 33, [45, 5, 6, [ 79, 81, [ 33, 21 ] ] ] ] ];
const flat = function(arr) {
return arr.reduce((pre,cur)=> {
return pre.concat(Array.isArray(cur)? flat(cur) : cur);
}, []);
}
const flatArr = flat(arr);
const noRepeatArr = Array.from(new Set(flatArr));
const max = noRepeatArr.sort((a,b) => a - b)[noRepeatArr.length - 1];
/reduce也可/
8. js执行上下文和作用域链
- 每个函数执行的时候都会创建一个执行上下文
- 为了管理这些执行上下文,js引擎创建了一个执行上下文栈,用来管理上下文
- 默认执行的时候会有一个全局上下文,只有在浏览器关闭的时候全局上下文才会被销毁
- 执行上下文有三个很重要的属性:变量对象、作用域链、this指向
- 作用域链:
- 作用域是在函数定义的时候就决定了
- 函数会保存一个内部属性:[[scope]],里面保存了所有父变量对象
- 总结: 执行上下文栈存放着执行上下文,函数内部会保存[[scope]]属性会保存所有的父变量对象,而且在函数执行的时候会把函数AO对象加进去,函数执行的时候会先去找自己的AO对象,找不到就通过作用域链向上找
function a() {
function b() {
function c() {
}
}
}
var a = 1;
function sum() {
var b = 2;
return a + b;
}
sum();
9. 什么是变量提升
- 带var和带function关键字的会导致变量提升
- 说一下js作用域:
- 在没有let之前: 产生作用域是三种情况: 全局/函数作用域/ eval
- js作用域叫做静态作用域。是静态的。
- 就是说函数在定义的时候产生函数作用域
- 函数执行的时候 产生执行上下文ECS
- 上下文分为两类: 全局上下文 和 函数上下文
- 上下文中包含三个重要的特点: 变量对象/作用域链/this
- 执行上下文周期分为创建阶段和代码执行阶段
- 预编译(创建阶段) 带var和带function关键字 先解析出来
10. js中类型转化的规则
11. 深浅拷贝
function cloneDeep(obj, hash = new WeakMap()) {
if(obj == null) { return obj }
if(obj instanceof Date) { return new Date(obj) }
if(obj instanceof RegExp) { return new RegExp(obj) }
if(typeof obj !== 'object') { return obj }
if(hash.get(obj)) { return hash.get(obj) }
let cloneObj = new obj.constructor();
hash.set(obj, cloneObj);
for(let key in obj) {
if(obj.hasOwnProperty(key)) {
cloneObj[key] = cloneDeep(cloneObj[key], hash)
}
}
return cloneObj;
}
12. 原型和原型链
13. 实现(5).add(3).minus(2), 使输出结果为6
- 5+3-2=6
- 考察类和实例,以及在类的原型上构建方法并实现链式写法
- 想实现实例调取方法:将方法放到实例的原型上
- 想实现链式调用:在方法中返回类的实例
~function(){
function check(n) {
n = Number(n);
return isNaN(n)? 0 : n;
}
function add(n) {
n = check(n);
return this + n
}
function minus(n) {
n = check(n);
return this - n;
}
Number.prototype.add = add;
Number.prototype.minus = minus;
}()
14. 箭头函数和普通函数的区别是什么
document.body.onclick = () => {
}
document.body.onclick = function(){
arr.sort(function(a,b) {
return a - b;
})
}
const func = (...arg) => {
console.log(arg);
}
15. 如何把一个字符串的大小写取反?(例如"aBc"变成"AbC")
let str = "adhasdFAAGXV的时间表jJN";
str = str.replace(/[a-zA_Z]/g, content => {
return content.toUpperCase() === content ? content.toLowerCase() : content.toUpperCase()
})
16.实现一个字符串匹配算法,从字符串s中查找是否存在字符串t,若存在返回所在位置,不存在返回-1
- (如果不能基于indexOf/includes等内置的方法,你会如何处理)
~function(){
function myIndexOf(T) {
let lenT = T.length;
let lenS = S.length;
res = -1;
if( lenT > lenS ) return -1;
for( let i = 0; i < lenS - lenT; i ++) {
if(this.substr(i, lenT) === T) {
res = i;
break;
}
}
return res;
}
String.prototype.myIndexOf = myIndexOf;
}()
let S = "hahayangguang";
let T = "yang";
console.log(S.myIndexOf(T));
~function(){
function myIndexOf(T) {
let reg = new RegExp(T);
let res = reg.exec(this);
return res === null? -1 : res.index;
}
String.prototype.myIndexOf = myIndexOf;
}()
17. 在输入框中如何判断输入的是一个正确的网址, 如用户输入一个字符串验证是否符合URL网址格式
let str = "http://www.baidu.cn/index.html?lx=1&from=wx#vedio";
let reg = /^((http|https|ftp):\/\/)?(([\w-]+\.)+[a-z0-9]+)((\/[^/]*)+)?(\?[^#]+)?(#.+)?$/i;
18. 执行题
function Foo() {
Foo.a = function() {
console.log(1)
}
this.a = function() {
console.log(2)
}
}
Foo.prototype.a = function() {
console.log(3)
}
Foo.a = function() {
console.log(4)
}
Foo.a();
let obj = new Foo();
obj.a();
Foo.a();