首页/文章/八股文

前端面试必须掌握的手写题:基础篇

2023-11-30
18367 分钟
...

金九银十过了大半,笔者最近也面了一些公司,现将一些自己遇到的和收集的基础题目整理出来,后续会整理分享一些其他的信息,希望对你能有所帮助

前端面试必须掌握的手写题:基础篇

前端面试必须掌握的手写题:场景篇

前端面试必须掌握的手写题:进阶篇

闲言少叙,看正文

实现Object.create

创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。 --MDN

function create(obj) {
  function Fun(){
    
  }
  Func.prototype = obj;
  Func.prototype.constructor = Func;
  return new Fun();
}

实现instanceof方法

instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。

function myInstanceof(obj, ctor) {
  let proto = Object.getPrototypeOf(obj);
  let prototype = ctor.prototype;
  where(true) {
    if(!proto) return false;
    if(proto === prototype) return true;
    proto = Object.getPrototypeOf(proto);
  }
}

实现new关键字

在调用new之后会发生这几个步骤

  1. 创建一个空对象

  2. 设置原型:将空白对象的原型设置为函数的prototype对象

  3. 让函数的this指向这个对象,执行构造函数的代码(为空白对象添加属性)

  4. 判断函数的返回值

    4.1. 如果是引用类型,直接返回,比如构造函数主动返回了一个对象:function T(){return {x: 1}}

    4.2. 如果不是引用类型,返回空白对象; 比如构造函数返回一个数字:function T(){return 1}


function objectFactory() {
  let newObject = null;
  let constructor = Array.prototype.shift.call(arguments);
  let result = null;
  if (typeof constructor !== 'function') {
    console.error('type error');
    return
  }
  newObject = Object.create(constructor.prototype);
  result = constructor.apply(newObject, arguments);
  let flag = result && (typeof result === 'function' || typeof result === 'object');
  return flag ? result : newObject;
}

拦截构造函数调用

function Person(name) {
  if (new.target !== undefined) {
    this.name = name;
  } else {
    throw new Error('必须使用 new 命令生成实例');
  }
}


function Person(name) {
  if (new.target === Person) {
    this.name = name;
  } else {
    throw new Error('必须使用 new 命令生成实例');
  }
}

var person = new Person('张三'); 
var notAPerson = Person.call(person, '张三');  

实现继承

组合式继承


function Father(name, age) {
  this.name = name
  this.age = age
  this.hobby = ['敲代码', '解Bug', '睡觉']
}

Father.prototype.sayName = function () {
  console.log(this.name, 666)
}
Father.prototype.x = 1

function Child(name, age) {
  Father.call(this, name, age) 
  this.a = 1
}
Child.prototype = Object.create(Father.prototype)


function Super(foo) {
  this.foo = foo
}
Super.prototype.printFoo = function() {
  console.log(this.foo)
}
function Sub(bar) {
  this.bar = bar
  Super.call(this)
}
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub

ES6版本继承

class Super {
  constructor(foo) {
    this.foo = foo
  }
  printFoo() {
    console.log(this.foo)
  }
}
class Sub extends Super {
  constructor(foo, bar) {
    super(foo)
    this.bar = bar
  }
}

简单实现Promise

这里简单实现一下,可以参考一下其他的Promise A+规范的实现,主要包含then,all,race

  • then:
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';

function MyPromise(fn) {
  const self = this;
  this.state = PENDING;
  this.value = null;
  this.reason = null;
  this.resolvedCallbacks = [];
  this.rejectedCallbacks = [];

  function resolve(value) {
    if (value instanceof MyPromise) {
      value.then(resolve, reject)
    }
    
    setTimeout(() => {
      if (self.state === PENDING) {
        self.state = RESOLVED;
        self.value = value;
        self.resolvedCallbacks.forEach(cb => cb(value));
      }
    }, 0)
  }

  function reject(reason) {
    setTimeout(() => {
      if (self.state === PENDING) {
        self.state = REJECTED;
        self.reason = reason;
        self.rejectedCallbacks.forEach(cb => cb(reason));
      }
    }, 0)
  }
  try {
    fn(resolve, reject);
  } catch (e) {
    reject(e);
  }
}

MyPromise.prototype.then = function (onFulfilled, onReject) {
  const self = this;
  return new MyPromise((resolve, reject) => {
    let fulfilled = () => {
      try {
        const result = onFulfilled(self.value);
        return result instanceof MyPromise ? result.then(result) : resolve(result);
      } catch (e) {
        reject(e);
      }
    };
    let rejected = () => {
      try {
        const result = onReject(self.reason);
        return result instanceof MyPromise ? result.then(resolve, reject) : reject(result);
      } catch (e) {
        reject(e);
      }
    }
    switch (self.state) {
      case PENDING:
      case RESOLVED:
      case RESOLVED:

    }
  })
}

MyPromise.all = (promises) => {
  return new MyPromise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      throw new TypeError('arguments must be array');
    }
    let resolvedCounter = 0;
    let promiseNum = promises.length;
    let resolvedResult = [];

    for (let i = 0; i < promises.length; i++) {
      MyPromise.resolve(promises[i]).then(value => {
        resolvedCounter++;
        resolvedResult[i] = value;
        if (resolvedCounter === promiseNum) {
          return resolve(resolvedResult);
        }
      }, error => {
        return reject(error);
      })
    }
  })
}
MyPromise.race = function(args) {
  return new Promise((resolve, reject) => {
    for(let i = 0; len = args.length; i++) {
      args[i].then(resolve, reject);
    }
  })
}

防抖函数

防抖是n秒内会重新计时

function debounce(fn, wait) {
  let timer = null;
  return function() {
    if(timer) {
      clearTimeout(timer);
      timer = null;
    }
    timer = setTimeout(() => {
      fn.apply(this, arguments);
    }, wait);
  }
}

节流函数

n秒内不重新计时

function throttle(fn, delay) {
  let timer = null;
  return function () {
    if (timer) return;
    timer = setTimeout(() => {
      timer = null;
      return fn.apply(this, arguments);
    }, delay)
  }
}

实现类型判断函数

function getType(value) {
  if (value === null) {
    return value + '';
  }
  if(typeof value === 'object') {
    return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
  } else {
    return typeof value;
  }
}

实现call函数

执行步骤:

  • 判断call的调用者是否为函数,不是函数需要抛出错误,call调用者就是上下文this,也就是需要被调用的函数
  • 判断需要被调用的函数的的上下文对象是否传入,不存在就设置为window
  • 处理传入的参数,截取第一个参数后的所有参数,作为被调用函数
  • 将需要被调用的函数,绑在传入的上下文上,作为一个属性
  • 使用传入的上下文调用这个函数,并返回结果
  • 删除绑定的属性
  • 返回结果

Function.prototype.myCall = function(context) {
  if(typeof this !== 'function') {
    throw new TypeError('need function');
  }
  let args = arguments.slice(1);
  let result = null;

  context = context || window;

  context.fn = this;
  result = context.fn(...args);

  delete context.fn;
  return result;
}

实现apply函数

唯一的不同就是最后参数的获取方式

Function.prototype.myApply = function(context) {
  if(typeof this !== 'function') {
    throw new TypeError('need function');
  }
  let args = arguments[1];
  let result = null;

  context = context || window;

  context.fn = this;
  result = context.fn(...args);

  delete context.fn;
  return result;
}

实现bind

  • 先判断调用者是否为函数

  • 缓存当前需要bind的函数,就是上面的调用者,也是是bind函数的上下文

  • 返回一个函数,利用闭包原理实现对this的保存

  • 函数内部用apply函数来处理函数调用

    • 需要判断函数作为构造函数的情况,这个时候的this就是当前调用这个闭包函数的this
    • 作为普通函数,直接使用传入的上下文就好了
Function.prototype.myBind = function(context) {
  if(typeof this !== 'function') {
    throw new TypeError('need function');
  }
  let args = [...arguments].slice(1);
  let fn = this;

  return function F() {
    return fn.apply(
      this instanceof F ? this : context,
      args.concat(...arguments)
    )
  }
}

浅拷贝


Object.assign(target, source1, source2);


{...obj1, ...obj2}


Array.prototype.slice
Array.prototype.concat


function shallowCopy(object) {
  if(!object || typeof object !== 'object') return;
  let newObj = Array.isArray(object);

  for(let key in object) {
    if(object.hasOwnProperty(key)) {
      newObj[key] = object(key);
    }
  }
  return newObj;
}

深拷贝deepclone

可能的问题:

  • json方法出现函数或symbol类型的值的时候,会失效
  • 处理循环引用问题
  • 处理可迭代类型的数据
  • 处理包装类型
  • 处理普通类型

简单版本参考vue版本:

  • 判断类型是否为原始类型,如果是,无需拷贝,直接返回
  • 为避免出现循环引用,拷贝对象时先判断存储空间中是否存在当前对象,如果有就直接返回
  • 开辟一个存储空间,来存储当前对象和拷贝对象的对应关系
  • 对引用类型递归拷贝直到属性为原始类型
const deepClone = (target, cache = new WeakMap()) => {
    if(target === null || typeof target !== 'object') {
        return target
    }
    if(cache.get(target)) {
        return target
    }
    const copy = Array.isArray(target) ? [] : {}
    cache.set(target, copy)
    Object.keys(target).forEach(key => copy[key] = deepClone(obj[key], cache))
    return copy
}

实现Object.assign

就是实现一个浅拷贝


Object.myAssign = function (target, ...source) {
  if (target === null) {
    throw new TypeError('can not be null');
  }
  let ret = Object(target);
  source.forEach(obj => {
    if (!obj !== null) {
      for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
          ret[key] = obj[key];
        }
      }
    }
  });
  return ret;
}

简单实现async/await中的async函数

async/await语法糖就是使用Generator函数+自动执行器来运作的,注意只要要实现async函数就是实现一个generate函数+执行器的语法糖


function getNum(num){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(num+1)
        }, 1000)
    })
}


function asyncFun(func){
  var gen = func();

  function next(data){
    var result = gen.next(data);
    if (result.done) return result.value;
    result.value.then(function(data){
      next(data);
    });
  }

  next();
}


var func = function* (){
  var f1 = yield getNum(1);
  var f2 = yield getNum(f1);
  console.log(f2) ;
};
asyncFun(func);

实现一个Object.freeze

锁定对象的方法

  • Object.preventExtensions()

no new properties or methods can be added to the project 对象不可扩展, 即不可以新增属性或方法, 但可以修改/删除

  • Object.seal()

same as prevent extension, plus prevents existing properties and methods from being deleted 在上面的基础上,对象属性不可删除, 但可以修改

  • Object.freeze()

same as seal, plus prevent existing properties and methods from being modified 在上面的基础上,对象所有属性只读, 不可修改

以上三个方法分别可用Object.isExtensible(), Object.isSealed(), Object.isFrozen()来检测

var deepFreeze =function (obj) {
	var allProps = Object.getOwnPropertyNames(obj);
	
	allProps.forEach(item => {
		if (typeof obj[item] === 'object') {
			deepFreeze(obj[item]);
		}
	});
	return Object.freeze(obj);
}

模拟实现一个Object.freeze,使用了Object.seal

 function myFreeze(obj) {
    if (obj instanceof Object) {
      Object.seal(obj);
      let p;
      for (p in obj) {
        if (obj.hasOwnProperty(p)) {
          Object.defineProperty(obj, p, {
            writable: false
          });
         myFreeze(obj[p]);
       }
     }
   }
 }

用ES5实现一下map和reduce函数

Array.prototype.myMap = (fn, context) => {
  var arr = Array.prototype.slice.call(this);
  var mapArray = [];
  for (let i = 0; i < arr.length; i++) {
    mapArray.push(fn.call(context, arr[i], i, this));
  }
  return mapArray;
}

Array.prototype.myReduce = (fn, initialValue) => {
  var arr = Array.prototype.slice.call(this);
  var res, startIndex;
  res = initialValue ? initialValue : arr[0];
  startIndex = initialValue ? 0 : 1;
  for(let i = startIndex; i< arr.length; i++) {
    res = fn.call(null, res, arr[i], i, this);
  }
  return res;
}

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
八股文2026-08-27
定时器按顺序播放多个音频,切后台音频会乱
本身不会补触发,但 它会被系统级冻结 ——iOS Safari 后台完全停止计时,切回前台后 只补执行一次 (不是不补,而是"缺失的中间状态补不上")。间隔短的不会出大问题, 间隔长的会出现"音频流被压缩" ——切回来后本该播 5 分钟的间隙,实际只过了 3 分钟,结果整段对不上。 核心原则 :定时器只能用来"提醒一次", 真正决定"该不该播"的是绝对时间戳 。 setTimeout 在后台会发生什么(按平台) | 平台 | 后台行为...
面试
八股文2026-08-27
线上项目白屏的原因
白屏的本质是 渲染管线某一环断了 ——可能是资源、JS、接口、路由、样式、兼容性任一环节出问题。排查按"控制台 → 网络 → DOM → 环境"四步定位。 本质(前端类比) 把网页想成一栋楼: 白屏 ≠ 一定是同一种原因——这是面试想听的层次。 六大类原因(按出现频率) | 类别 | 典型表现 | 真实案例 | | : | : | : | | ① 资源加载失败 | DOM 是空的 | 入口 JS 404、CDN 挂了、CSS 阻塞 |...
面试
八股文2026-08-27
背景图就是 1MB 大图,怎么优化
1MB 大背景图优化分 三步 :压缩体积(10 30x)、按需加载(按设备/视口/网速)、渲染期优化(GPU 合成)。背景图跟 不同——它是 CSS,不走浏览器的原生懒加载机制,得手动处理。 背景图 vs 的关键区别(先讲清楚这个) 这是面试官想听的"针对性认知"——背景图不是普通图片,不能套通用方案。 三步优化(按优先级) 第 1 步:压缩体积(最重要,立竿见影) 1MB 的来源一般是这几种 ,对应解决方案: | 原始问题 | 体积来...
面试
八股文2026-08-27
项目里很多图片和视频,怎么优化
图片视频优化分 四层 :网络层(CDN/格式)、加载层(懒加载/预加载)、渲染层(解码/缓存)、业务层(按需/降级)。面试要把这四层都讲到位才算有体系。 四层优化模型 第 1 层:网络层(省钱、省时间) 核心目标:让资源体积小、让用户拿到资源快 | 手段 | 作用 | 关键点 | | : | : | : | | 图片格式 | WebP/AVIF 比 JPEG 小 25 50% | 兼容 fallback | | 视频格式 | H.265...
面试
八股文2026-08-20
Embedding 向量模型:从语义表示到相似度计算
前言 大模型「读懂」文字靠的是 token,但 token 之间只有离散的编号关系,模型并不知道「苹果」和「李子」在语义上很近。要让程序能「理解」两段文字的相似程度,必须先把文本映射成一个 高维向量 ,再用几何方法比较。这一步就是 Embedding。 本篇基于我本地 今天的真实代码,从语义表示讲到余弦相似度,并复盘几个踩过的真实坑。 一、为什么需要 Embedding 传统关键词检索是「字面匹配」: Embedding 做的是「语义匹...
面试
八股文2026-08-19
前端面试100题
前端面试 100 题 适用方向:中高级前端 / React / Vue / Next.js / Nuxt / TypeScript / 工程化 / 实时通信 / Electron / Node.js / AI 应用前端 使用方式:优先掌握“标准回答”,再练“面试官追问”,最后把“结合你的简历怎么答”组织成自己的项目故事。 说明 “结合你的简历怎么答”只使用你简历中已经出现的项目与技术事实。 “标准回答 / 追问”属于通用前端知识总结,用...
面试

评论

请登录后发表评论

去登录
加载评论中...

目录