跳到主要内容

JS 经典笔试面试题

一些迈向高级及以上职级所需要掌握的经典面试题,重点是考察 JS 原理及应用。大公司可能会有现场编程要求,面试的时候会随机整几道经典题现场编程。如果是对数据结构和算法有特别要求的,则会有更多的算法类的题目。

call/apply

说明:call/apply 立即执行函数,同时函数中的 this 改为指向 context。类似等价于以下

Function.prototype.call = function(context, ...args) {
context = context || window;
context.fn = this; // 这里的this代表函数
context.fn(...args); // 给context添加属性fn,所以执行fn方法时,里面的this代表context
delete context.fn;
};

Function.prototype.apply = function(context, ...args) {
context = context || window;
context.fn = this;
context.fn(args); // apply传递数组
delete context.fn;
};

bind

说明:通过 apply 改变 this,并且返回一个函数。

Function.prototype.bind = function(context, ...args) {
var fn = this;
return function() {
return fn.apply(context, args);
};
};

new

_new() {
var object = new Object() // 1. 类都是object类型
var Constructor = [].shift.call(arguments)
var args = arguments // 剩下的参数
object.__proto__ = Constructor.prototype // 2. 设置原型链
var ret = Constructor.apply(obj, args) // 3. 构造函数执行
return typeof ret === 'object' ? ret : obj
}
_new(Foo, 'name')

// es6
_new(Constructor, ...args) {
let object = Object.create(Constructor.prototype)
let ret = Constructor.apply(object, args)
return typeof ret === 'object' ? ret : obj
}

浅拷贝/深拷贝

自己创建一个新的对象,来接受你要重新复制或引用的对象值。如果对象属性是基本的数据类型,复制的就是基本类型的值给新对象;但如果属性是引用数据类型,复制的就是内存中的地址,如果其中一个对象改变了这个内存中的地址,肯定会影响到另一个对象。

一些 JavaScript 提供的浅拷贝方法:

  • object.assign

    但是使用 object.assign 方法有几点需要注意:

    • 它不会拷贝对象的继承属性;
    • 它不会拷贝对象的不可枚举的属性;
    • 可以拷贝 Symbol 类型的属性。
  • 扩展运算符方式

  • concat 拷贝数组

  • slice 拷贝数组

将一个对象从内存中完整地拷贝出来一份给目标对象,并从堆内存中开辟一个全新的空间存放新对象,且新对象的修改并不会改变原对象,二者实现真正的分离。

深拷贝方法:

  • JSON.stringify + JSON.parse

    JSON.stringify() 是目前开发过程中最简单的深拷贝方法,其实就是把一个对象序列化成为 JSON 的字符串,并将对象里面的内容转换成字符串,最后再用 JSON.parse() 的方法将JSON 字符串生成一个新的对象。不过还需要注意一些问题:

    • 拷贝的对象的值中如果有函数、undefined、symbol 这几种类型,经过 JSON.stringify 序列化之后的字符串中这个键值对会消失;
    • 拷贝 Date 引用类型会变成字符串;
    • 无法拷贝不可枚举的属性;
    • 无法拷贝对象的原型链;
    • 拷贝 RegExp 引用类型会变成空对象;
    • 对象中含有 NaN、Infinity 以及 -Infinity,JSON 序列化的结果会变成 null;
    • 无法拷贝对象的循环应用,即对象成环 (obj[key] = obj)。
function Obj() { 
this.func = function () { alert(1) };
this.obj = {a:1};
this.arr = [1,2,3];
this.und = undefined;
this.reg = /123/;
this.date = new Date(0);
this.NaN = NaN;
this.infinity = Infinity;
this.sym = Symbol(1);
}
let obj1 = new Obj();
Object.defineProperty(obj1,'innumerable',{
enumerable:false,
value:'innumerable'
});
console.log('obj1',obj1);
let str = JSON.stringify(obj1);
let obj2 = JSON.parse(str);
console.log('obj2',obj2);

去重

// before: [2, 1, 3, 2]
// after: [2, 1, 3]

function removeRepeat(arr) {
return arr.filter((item, index) => arr.indexOf(item) === index);
}

// or es6
let removeRepeat = (arr) => Array.from(new Set(arr));
let removeRepeat = (arr) => [...new Set(arr)];

防抖/节流

ajax

ajax.get = function(url) {
return new Promise(function(resolve, reject) {
let xhr = new XMLHttpRequest();
xhr.open("get", url, true);
xhr.onreadystatechange = function() {
if (this.readyState === 4) {
if (this.status === 200) {
resolve(this.response, this);
} else {
reject({ response: this.response, code: this.status });
}
}
};
xhr.send();
});
};

get("httpURL").then((value, that) => {
console.log(value);
});

curry 柯里化

说明:递归,当执行的参数个数等于原本函数的个数,执行函数。

var curry = function(fn) {
var limit = fn.length; // fn函数参数个数
return function judgeCurry(...args) {
if (args.length >= limit) {
return fn.apply(null, args);
} else {
return function(...args2) {
return judgeCurry.apply(null, args.concat(args2));
};
}
};
};

// or es6
var curry = function(fn, ...args) {
if (args.length >= fn.length) {
return fn(...args);
}

return function(...args2) {
return curry(fn, ...args, ...args2);
};
};

模拟 Promise

图片懒加载

// 方法一
function inSight(el) {
const bound = el.getBoundingClientRect();
const height = window.innerHeight;

return bound.top < height;
}

const imgs = document.querySelectorAll(".my-photo"); // 需要懒加载的图片类

function checkImg() {
console.log(1);
imgs.forEach((img) => {
if (inSight(img)) {
loadImg(img);
}
});
}

function loadImg(el) {
if (!el.src) {
const source = el.dataset.src;
el.src = source;
}
}

function throttle(fn, wait = 100) {
let pre;
return function() {
if (!pre) {
pre = +new Date();
}
let now = +new Date();
if (now - pre > wait) {
pre = now;
fn();
}
};
}

window.onscroll = throttle(checkImg);

// 方法二,推荐
function checkImgs() {
Array.from(document.querySelectorAll(".my-photo")).forEach((item) =>
io.observe(item)
);
}

function loadImg(el) {
if (!el.src) {
const source = el.dataset.src;
el.src = source;
}
}

const io = new IntersectionObserver((ioes) => {
ioes.forEach((ioe) => {
const el = ioe.target;
const intersectionRatio = ioe.intersectionRatio;
if (intersectionRatio > 0 && intersectionRatio <= 1) {
loadImg(el);
}
el.onload = el.onerror = () => io.unobserve(el);
});
});

pipe/compose

flatten

发布订阅/依赖者模式

前端必备设计模式案例。

// 发布订阅
const event = {
obj: {},
on: function(name, fn) {
(this.obj[name] || this.obj[name] = []).push(fn);
},
emit: function(name, ...args) {
if (Array.isArray(this.obj[name])) {
this.obj[name].forEach((fn) => fn.call(this, ...args));
}
},
};

// 依赖者模式
function Dep() {
this.watchers = [];
}
Dep.prototype.depend = (watcher) => this.watchers.push(watcher);
Dep.prototype.notify = () => this.watchers.forEach((w) => w.update());

function Watcher(fn) {
this.fn = fn;
}
Watcher.prototype.update = function() {
this.fn();
};
const dep = new Dep();
dep.depend(new Watcher(function() {}));
dep.notify();

实现模板引擎