前端面试——手写代码题

✨ call、apply、bind

call,apply,bind 都是为了改变函数运行时上下文(this指向)而存在的

  • 以上三个函数接收的第一个参数都是 要绑定的this指向.
  • apply的第二个参数是一个参数数组,callbind的第二个及之后的参数作为函数实参按顺序传入。
  • bind不会立即调用,其他两个会立即调用。

手写call

Function.prototype.myOwnCall = function(context, ...args) {
    context = context || window;
    let fn = Symbol('fn');
    context[fn] = this;

    const result =  thisArg[fn](...args);
    delete context[fn];
    return result;
}

手写apply

Function.prototype.myOwnApply = function(context, arr) {
    context = context || window
    let fn = Symbol('fn');
    context[fn] = this;

    var args = [];
    var result = null;

    if (!arr) {
    result = context[fn]();
    } else {
    result = context[fn](arr);
    }
    delete context[fn];
    return result;
}

手写bind

把新的 this 绑定到某个函数 func 上,并返回 func 的一个拷贝

let boundFunc = func.bind(thisArg[, arg1[, arg2[, ...argN]]])
  • 初级

✅ 使用 ES6 语法实现

❌ 不兼容 IE,不支持 new

// 初级:ES6 新语法 const/...
function bind_1(asThis, ...args) {
  const fn = this; // 这里的 this 就是调用 bind 的函数 func
  return function (...args2) {
    return fn.apply(asThis, ...args, ...args2);
  };
}
  • 中级

✅ 兼容 IE

❌ 不支持 new

function bind2(asThis) {
  var slice = Array.prototype.slice;
  var args = slice.call(arguments, 1);
  var fn = this;
  if (typeof fn !== "function") { // 加入了对调用函数类型的判断
    throw new Error("cannot bind non_function");
  }
  return function () {
    var args2 = slice.call(arguments, 0);
    return fn.apply(asThis, args.concat(args2));
  };
}
  • 高级
function bind3(thisArg, ...args) {
	const originFunc = this;
  const boundFunc = function (...args1) {
      // 解决 bind 之后对返回函数 new 的问题
      if (new.target) {
          if (originFunc.prototype) {
              boundFunc.prototype = originFunc.prototype;
          }
          const res = originFunc.apply(this, args.concat(args1));
          return res !== null && (typeof res === 'object' || typeof res === 'function') ? res : this;
      } else {
          return originFunc.apply(thisArg, args.concat(args1));
      }
  };
  // 解决length 和 name 属性问题
  const desc = Object.getOwnPropertyDescriptors(originFunc);
  Object.defineProperties(boundFunc, {
      length: Object.assign(desc.length, {
          value: desc.length < args.length ? 0 : (desc.length - args.length)
      }),
      name: Object.assign(desc.name, {
          value: `bound ${desc.name.value}`
      })
  });
  return boundFunc;
};

✨ 手写深拷贝

简单版

JSON反序列化

const B = JSON.parse(JSON.stringify(A))

❌ JSON value不支持的数据类型,都拷贝不了:

  1. 不支持函数
  2. 不支持undefined(支持null
  3. 不支持循环引用,比如 a = {name: 'a'}; a.self = a; a2 = JSON.parse(JSON.stringify(a))
  4. 不支持Date,会变成 ISO8601 格式的字符串
  5. 不支持正则表达式
  6. 不支持Symbol

复杂版

  1. 第一版

        function isObject(obj){
            return Object.prototype.toString.call(obj) === '[Object Object]';
        }
        function clone(source){
        if(!isObject(source)) return source;
        const target = {};
    
        for(let i in target) {
            if(source.hasOwnProperty(i)){
                if(isObject(source[i])){
                    target[i] = clone(source[i]);
                } else {
                    target[i] = source[i];
                }
            }
        }
    
        return target;
    }
    
    • 没有对参数做检验
    • 判断是否对象的逻辑不够严谨
    • 没有考虑数组的兼容
  2. 第二版

    function isObject(obj){
        return typeof obj === 'object' && obj != null; // 兼容数组
    }
    // 使用 循环检测 解决循环引用
    function clone(source, hash = new WeakMap()){
        if(!isObject(source)) return source;
        if (hash.has(source)) return hash.get(source); // 新增代码,查哈希表
    
        const target = Array.isArray(source) ? [...source] : {...source};
        hash.set(source, target); // 新增代码,哈希表设值
    
    	for(let i in target) {
                if(target.hasOwnProperty(i)){
                    if(isObject(target[i])){
                        target[i] = clone(target[i], hash); // 新增代码,传入哈希表
                    } 
                 }
            }
    	return target;
    }
    // 这种方法还可以解决引用丢失问题
    

    没有解决递归爆栈。

  3. 第三版

    将递归改为循环来破解爆栈

    function isObject(obj){
        return typeof obj === 'object' && obj != null; // 兼容数组
    }
    
    function clone(source, hash = new WeakMap()){
        const root = {};
    	// 栈
        const loopList = [{
            parent: root,
            key: undefined,
            data: source,
        }];
    
        while(loopList.length){
            // 深度优先
            const node = loopList.pop();
            const parent = node.parent;
            const key = node.key;
            const data = node.data;
            // 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
            let res = parent;
            if (typeof key !== 'undefined') {
                res = parent[key] = {};
            }
            // 数据已经存在
            if (hash.has(data)) {
                parent[key] = hash.get(data);
                continue; // 中断本次循环
            }
    
            // 数据不存在
            // 保存源数据,在拷贝数据中对应的引用
            hash.set(data, res);
    
    	for(let i in data) {
                if(data.hasOwnProperty(i)){
    		if(isObject(data[i])){
                      // 下一次循环
                      loopList.push({
                          parent: res,
                          key: i,
                          data: data[i],
                      });
                      } else {
                        res[k] = data[k];
                      }
                    }
                }
    	}
    	return root;
    }
    

✨ 手写防抖&节流

  • 防抖
    • 按钮提交场景:防止多次提交按钮,只执行最后提交的一次
    • 服务端验证场景:表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能类似
  • 节流
    • 拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动
    • 缩放/滚动场景:监控浏览器resize,监控scroll高度
    • 动画场景:避免短时间内多次触发动画引起性能问题
// 防抖函数 debounce
const debounce = (fn, delay = 500) => {
	let timer = null;
	return (...args) => {
		if (timer) clearTimeout(timer)
		timer = setTimeout(()=>{
			fn.apply(this, args);
		}, delay)
	}
}
// 进阶版 debounce
// leading 表示进入时是否立即执行
const debounce = function(fn, delay = 500, options: {leading: true, context: null}){
	let timer = null;
	let res;
	const _debounce = function (...args) {
		options.context || (options.context = this);  
		if(timer) clearTimeout(timer);
		if(options.leading && !timer){
			timer = setTimeout(()=>{timer = null}, delay);
			res = fn.apply(option.context, args);
		} else {
			timer = setTimeout(() => {
				res = fn.apply(option.context, args);
				timer = null;
			}, delay);
		}
		return res;
  }
	_debounce.cancle = function(){
		clearTimeout(timer);
		timer = null;
	}
	return _debounce;
}

// 节流函数 throttle
// fn 是需要执行的函数
// wait 是时间间隔
const throttle = (fn, wait = 50) => {
  // 上一次执行 fn 的时间
  let previous = 0
  // 将 throttle 处理结果当作函数返回
  return function(...args) {
    // 获取当前时间,转换成时间戳,单位毫秒
    let now = +new Date()
    // 将当前时间和上一次执行函数的时间进行对比
    // 大于等待时间就把 previous 设置为当前时间并执行函数 fn
    if (now - previous > wait) {
      previous = now
      fn.apply(this, args)
    }
  }
}

// 进阶版 throttle
// leading 表示进入时是否立即执行,trailing 表示是否在最后额外触发一次
const throttle = (fn, wait  = 50, options: {leading: true, trailing: false, context: null}){
	let previous = 0;
	let res = ;
	let timer;
	const _throttle = function(...args){
		options.context || (options.context = this);  
		let now = Date.now();
		if(!previous && !options.leading) previous = now;
		if(now - previous >= wait){
			if (timer) {
	        clearTimeout(timer);
	        timer = null;
      }
			res = fn.apply(options.context, args);
			previous = now;
		} else if(!timer && options.trailing){
			timer = setTimeout(()=>{
				res = fn.apply(options.context, args);
				previous = 0;
        timer = null;
			}, wait);
		}
		return res;
	}
	_throttle.cancel = function () {
      previous = 0;
      clearTimeout(timer);
      timer = null;
  };
  return _throttle;
}

// DEMO
// 执行 throttle 函数返回新函数
const betterFn = throttle(() => console.log('fn 函数执行了'), 1000)
// 每 10 毫秒执行一次 betterFn 函数,但是只有时间差大于 1000 时才会执行 fn
setInterval(betterFn, 10)

实现instanceOf

function instance_of(L, R) {
	let O = R.prototype;
	L = L.__proto__; // 取 L 的隐式原型,等同于 L = Object.getPrototypeOf(L);
	while(L){
		if(L === O) return true;
		L = L.__proto__;
	}
	return false;
}

//   等同于
function instance_of (L, R) {
 return right.prototype.isPrototypeOf(L);
};

实现new

new 执行过程如下:

  1. 创建一个新对象;
  2. 新对象的[[prototype]]特性指向构造函数的prototype属性;
  3. 构造函数内部的this指向新对象;
  4. 执行构造函数;
  5. 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象
const myOwnNew = (constructor, ...args) => {
	let target = {}

	target.__proto__ = constructor.prototype;  // 等同于 target = Object.create(constructor.prototype);
	const res = constructor.apply(target, args)

	return res instanceof Object ? res : target;
}
const a = function(){
	return 'a';
}
const a1 = myOwnNew(a);

实现Object.create

function create(obj){
    function f(){};
    f.proptotype = obj;
    return new f();
}

实现Object.is

Object.is() 和 === 的区别是 Object.is(0, -0) 返回 false, Object.is(NaN, NaN) 返回 true。

// polyfill
const iIs = function (x, y) {
    if (x === y) {
        return x !== 0 || 1 / x === 1 / y;  // 0, -0
    } else {
        return x !== x && y !== y;  // NaN
    }
}

实现flat

// reduce + 递归
function flat(arr, num = 1) {
  return num > 0
    ? arr.reduce(
        (pre, cur) =>
          pre.concat(Array.isArray(cur) ? flat(cur, num - 1) : cur),
        []
      )
    : arr.slice();
}
const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "弹铁蛋同学" }]
flat(arr, Infinity);
// [1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, "string", { name: "弹铁蛋同学" }];

函数柯里化

将一个多参数函数转化为多个嵌套的单参数函数。

const curry = function (targetFn) {
 return function fn (...rest) {
     if (targetFn.length === rest.length) {
            return targetFn.apply(null, rest);
        }  else {
            return fn.bind(null, ...rest);
        }
    };
};
// 用法
function add (a, b, c, d) {
    return a + b + c + d;
}
console.log('柯里化:', curry(add)(1)(2)(3)(4)); 
// 柯里化:10

实现CO(协程)

const co = (gen) =>{
    return new Promise((resolve, reject) => {
      if(typeof gen === 'function' ) gen = gen();
      if(!gen || typeof gen.next !== 'function') return resolve(gen);
      onFulfilled();

      function onFulfilled(v){
        let res;
        try {
          res = gen.next(v);
        } catch (error) {
          reject(error);
        }
        next(res);
      }
  
      function onRejected(err){
        let res;
        try {
          res = gen.throw(err);
        } catch (e) {
            return reject(e);
        }
        next(res);
      }
  
      function next(res){
        if(res.done) return resolve(res.value);
        let nextGen = Promise.resolve(res.value);
        return nextGen.then(onFulfilled, onRejected);
      }
      
    });
  }

// 测试
  const gen = function *() {
    console.log('start');
    let res1 = yield Promise.resolve(1);
    console.log(res1);
    let res2 = yield Promise.resolve(2);
    console.log(res2);
    let res3 = yield Promise.resolve(3);
    console.log(res3);
    return res1 + res2 + res3;
  }
  co(gen).then(value => {
    console.log('add: ' + value);
  }, function (err) {
    console.error(err.stack);
  });

原生XHR请求

XMLHttpRequest (XHR) 是一种创建 AJAX 请求的 JavaScript API 。

function reqListener () {
  console.log(this.responseText);
}

var xhr = new XMLHttpRequest();
xhr.addEventListener("load", reqListener); // 请求成功完成时触发
xhr.open("GET", "http://www.example.org/example.txt");  // 初始化一个请求 (method, url, async, user, password)
xhr.onloadstart() // 在XMLHttpRequest**开始传送数据**时被调用

xhr.onreadystatechange = function () {
  if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
    console.log(xhr.responseText);
  }
}
// 只要 readyState 属性发生变化,就会调用相应的处理函数。
/**
0	UNSENT	          代理被创建,但尚未调用 open() 方法。
1	OPENED	          open() 方法已经被调用。
2	HEADERS_RECEIVED	send() 方法已经被调用,并且头部和状态已经可获得。
3	LOADING	下载中     responseText 属性已经包含部分数据。
4	DONE	            下载操作已完成。
*/

xhr.send();  // 发送请求 (body)

实现Promise版JSONP

function jsonp({url, params, callback}) {
    return new Promise((resolve, reject) => {
      let script = document.createElement('script');
      window[callback] = function(data){
        resolve(data);
        document.body.removeChild(script);
      }
      params = {...params, callback};
      let arrs = [];
      for(key in params){
        arrs.push(`${key}=${params[key]}`);
      }
      script.src = `${url}?${arrs.join('&')}`;
      document.body.appendChild(script);
    });
  }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值