前端面试题-手写代码题(更新中)

1. 手写Object.create()

思路:将传入的对象作为原型

function objectCreate(obj){
  function F(){}
  F.prototype = obj
  return new F()
}

2.手写instanceof

(1)

instanceof原理实际上就是查找目标对象的原型链

function myInstance(L, R) {//L代表instanceof左边,R代表右边
   var RP = R.prototype
   var LP = L.__proto__
   while (true) {
     if(LP == null) {
       return false
     }
     if(LP == RP) {
       return true
     }
     LP = LP.__proto__
   }
 }
 console.log(myInstance({},Object)); 

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

  • 首先获取类型的原型
  • 然后获得对象的原型
  • 然后一直循环判断对象的原型是否等于类型的原型,直到对象原型为 null,因为原型链最终为 null
function myInstanceof(left, right) {
  let proto = Object.getPrototypeOf(left), // 获取对象的原型
      prototype = right.prototype; // 获取构造函数的 prototype 对象// 判断构造函数的 prototype 对象是否在对象的原型链上
  while (true) {
    if (!proto) return false;
    if (proto === prototype) return true;
​
    proto = Object.getPrototypeOf(proto);
  }
}

3.手写new

new内部:

  • 创建一个新对象
  • 使新对象的__proto__指向原函数的prototype
  • 改变this指向(指向新的obj)并执行该函数,执行结果保存起来作为result
  • 判断执行函数的结果是不是null或Undefined,如果是则返回之前的新对象,如果不是则返回result
 function myNew(fn,...args){
   // 创建一个空对象
   let obj = {}
   // 使空对象的隐式原型指向原函数的显示原型
   obj.__proto__ = fn.prototype
   // this指向obj
   fn.apply(obj,args)
   // 返回
   return obj
 }

(1)首先创建了一个新的空对象
(2)设置原型,将对象的原型设置为函数的 prototype 对象。
(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)
(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。

function myNew(fn, ...args) {
  // 判断参数是否是一个函数
  if (typeof fn !== "function") {
    return console.error("type error");
  }
  // 创建一个对象,并将对象的原型绑定到构造函数的原型上
  const obj = Object.create(fn.prototype);
  const value = fn.apply(obj, args); // 调用构造函数,并且this绑定到obj上
  // 如果构造函数有返回值,并且返回的是对象,就返回value ;否则返回obj
  return value instanceof Object ? value : obj;
}

4.防抖和节流

函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。

函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。

// //防抖
function debounce(fn, date) {
  let timer  //声明接收定时器的变量
  return function (...arg) {  // 获取参数
    timer && clearTimeout(timer)  // 清空定时器
    timer = setTimeout(() => {  //  生成新的定时器
      //因为箭头函数里的this指向上层作用域的this,所以这里可以直接用this,不需要声明其他的变量来接收
      fn.apply(this, arg) // fn()
    }, date)
  }
}
//--------------------------------
// 节流
function debounce(fn, data) {
  let timer = +new Date()  // 声明初始时间
  return function (...arg) { // 获取参数
    let newTimer = +new Date()  // 获取触发事件的时间
    if (newTimer - timer >= data) {  // 时间判断,是否满足条件
      fn.apply(this, arg)  // 调用需要执行的函数,修改this值,并且传入参数
      timer = +new Date() // 重置初始时间
    }
  }
}

5.手写 call 函数

call 函数的实现步骤:

  1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
  2. 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
  3. 处理传入的参数,截取第一个参数后的所有参数。
  4. 将函数作为上下文对象的一个属性。
  5. 使用上下文对象来调用这个方法,并保存返回结果。
  6. 删除刚才新增的属性。
  7. 返回结果。
// call函数实现
Function.prototype.myCall = function(context) {
  // 判断调用对象
  if (typeof this !== "function") {
    console.error("type error");
  }
  // 获取参数
  let args = [...arguments].slice(1),
      result = null;
  // 判断 context 是否传入,如果未传入则设置为 window
  context = context || window;
  // 将调用函数设为对象的方法
  context.fn = this;
  // 调用函数
  result = context.fn(...args);
  // 将属性删除
  delete context.fn;
  return result;
};


call和apply实现思路主要是:

  • 判断是否是函数调用,若非函数调用抛异常
  • 通过新对象(context)来调用函数
    • 给context创建一个fn设置为需要调用的函数
    • 结束调用完之后删除fn
Function.prototype.myCall = function (context) {
  // 先判断调用myCall是不是一个函数
  // 这里的this就是调用myCall的
  if (typeof this !== 'function') {
    throw new TypeError("Not a Function")
  }

  // 不传参数默认为window
  context = context || window

  // 保存this
  context.fn = this

  // 保存参数
  let args = Array.from(arguments).slice(1)   //Array.from 把伪数组对象转为数组

  // 调用函数
  let result = context.fn(...args)

  delete context.fn

  return result

}

6.手写 apply 函数

apply 函数的实现步骤:

  • 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
  • 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
  • 将函数作为上下文对象的一个属性。
  • 判断参数值是否传入
  • 使用上下文对象来调用这个方法,并保存返回结果。
  • 删除刚才新增的属性
  • 返回结果
// apply 函数实现
Function.prototype.myApply = function(context) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  let result = null;
  // 判断 context 是否存在,如果未传入则为 window
  context = context || window;
  // 将函数设为对象的方法
  context.fn = this;
  // 调用方法
  if (arguments[1]) {
    result = context.fn(...arguments[1]);
  } else {
    result = context.fn();
  }
  // 将属性删除
  delete context.fn;
  return result;
};


Function.prototype.myApply = function (context) {
// 判断this是不是函数
  if (typeof this !== "function") {
    throw new TypeError("Not a Function")
  }

  let result

  // 默认是window
  context = context || window

  // 保存this
  context.fn = this

  // 是否传参
  if (arguments[1]) {
    result = context.fn(...arguments[1])
  } else {
    result = context.fn()
  }
  delete context.fn

  return result
}


7.手写 bind 函数

bind 函数的实现步骤:

  1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
  2. 保存当前函数的引用,获取其余传入参数值。
  3. 创建一个函数返回
  4. 函数内部使用 apply 来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的 this 给 apply
    调用,其余情况都传入指定的上下文对象。
// bind 函数实现
Function.prototype.myBind = function(context) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  // 获取参数
  var args = [...arguments].slice(1),
      fn = this;
  return function Fn() {
    // 根据调用方式,传入不同绑定值
    return fn.apply(
      this instanceof Fn ? this : context,
      args.concat(...arguments)
    );
  };
};


bind实现思路

  • 判断是否是函数调用,若非函数调用抛异常
  • 返回函数
    • 判断函数的调用方式,是否是被new出来的
      • new出来的话返回空对象,但是实例的__proto__指向_thisprototype
  • 完成函数柯里化
    • Array.prototype.slice.call()
Function.prototype.myBind = function(context){
  // 判断是否是一个函数
  if(typeof this !== "function") {
    throw new TypeError("Not a Function")
  }
  // 保存调用bind的函数
  const _this = this 
  // 保存参数
  const args = Array.prototype.slice.call(arguments,1)
  // 返回一个函数
  return function F () {
    // 判断是不是new出来的
    if(this instanceof F) {
      // 如果是new出来的
      // 返回一个空对象,且使创建出来的实例的__proto__指向_this的prototype,且完成函数柯里化
      return new _this(...args,...arguments)
    }else{
      // 如果不是new出来的改变this指向,且完成函数柯里化
      return _this.apply(context,args.concat(...arguments))
    }
  } 
}

8.函数柯里化的实现

函数柯里化指的是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。


function curry(fn, ...args) {
  return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);
}

9.手写AJAX请求

创建AJAX请求的步骤:

  • 创建一个 XMLHttpRequest 对象。
  • 在这个对象上使用 open 方法创建一个 HTTP 请求,open 方法所需要的参数是请求的方法、请求的地址、是否异步和用户的认证信息。
  • 在发起请求前,可以为这个对象添加一些信息和监听函数。比如说可以通过 setRequestHeader 方法来为请求添加头信息。还可以为这个对象添加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5 个状态,当它的状态变化时会触发onreadystatechange 事件,可以通过设置监听函数,来处理请求成功后的结果。当对象的 readyState 变为 4 的时候,代表服务器返回的数据接收完成,这个时候可以通过判断请求的状态,如果状态是 2xx 或者 304 的话则代表返回正常。这个时候就可以通过 response 中的数据来对页面进行更新了。
  • 当对象的属性和监听函数设置完成后,最后调用 send 方法来向服务器发起请求,可以传入参数作为发送的数据体。
const SERVER_URL = "/server";
let xhr = new XMLHttpRequest();
// 创建 Http 请求
xhr.open("GET", SERVER_URL, true);
// 设置状态监听函数
xhr.onreadystatechange = function() {
  if (this.readyState !== 4) return;
  // 当请求成功时
  if (this.status === 200) {
    handle(this.response);
  } else {
    console.error(this.statusText);
  }
};
// 设置请求失败时的监听函数
xhr.onerror = function() {
  console.error(this.statusText);
};
// 设置请求头信息
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");
// 发送 Http 请求
xhr.send(null);


var Ajax = {
  get: function (url, callback) {
    let xhr = XMLHttpRequest();
    xhr.open("get", url, false)
    xhr.onreadystatechange = function () {
      if (xhr.readyState == 4) {
        if (xhr.status == 200 || xhr.status == 304) {
          console.log(xhr.responseText);
          callback(xhr.responseText)
        }
      }
    }
    xhr.send()
  },



  post: function (url, data, callback) {
    let xhr = new XMLHttpRequest()
    // 第三个参数为是否异步执行
    xhr.open('post', url, true)
    // 添加http头,设置编码类型
    xhr.setRequestHeader("Content-type","x-www-form-urlencoded")
    xhr.onreadystatechange = function () {
      if(xhr.readyState == 4) {
        if(xhr.status == 200 || xhr.status == 304) {
          console.log(xhr.responseText);
          callback(xhr.responseText)
        }
      }
    }
    xhr.setRequestHeader('Content-type', "application/x-www-urlencoded")
    xhr.send(data)
  }
}

10. 使用Promise封装AJAX请求

// promise 封装实现:
function getJSON(url) {
  // 创建一个 promise 对象
  let promise = new Promise(function(resolve, reject) {
    let xhr = new XMLHttpRequest();
    // 新建一个 http 请求
    xhr.open("GET", url, true);
    // 设置状态的监听函数
    xhr.onreadystatechange = function() {
      if (this.readyState !== 4) return;
      // 当请求成功或失败时,改变 promise 的状态
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    // 设置错误监听函数
    xhr.onerror = function() {
      reject(new Error(this.statusText));
    };
    // 设置响应的数据类型
    xhr.responseType = "json";
    // 设置请求头信息
    xhr.setRequestHeader("Accept", "application/json");
    // 发送 http 请求
    xhr.send(null);
  });
  return promise;
}

11. 手写深拷贝

function fn(obj) {
// 判断数据是否是复杂类型
  if (obj instanceof Object) {
    //判断数据是否是数组
    if (Array.isArray(obj)) {
      //声明一个空数组来接收拷贝后的数据
      let result = []
      obj.forEach(item => {
        // 需要递归深层遍历,否则复制的是地址
        result.push(fn(item))
      })
      // 返回输出这个数组,数组拷贝完成
      return result
    } else {
      //如果是对象,就声明一个空对象来接收拷贝后的数据
      let result = {}
      for (let k in obj) {
        // 使用递归深层遍历
        result[k] = fn(obj[k])
      }
      // 返回输出这个对象,对象拷贝完成
      return result
    }
  }
  // 简单数据类型则直接返回输出
  return obj
}

跨域方式

组件通信方式

继承的方法

JS数组去重方法

方法一:Set()+Array.from()/[…]
unique = (array) => {
  // return [...new Set(array)]
  return Array.from(new Set(array));
};
方法二:Map()
unique = (array) => {
  let map = new Map();
  let result = [];
  for (let i = 0; i < array.length; i++) {
    if (!map.has(array[i])) { // 如果map中没有该key,就加入 result 中
      map.set(array[i], true);
      result.push(array[i]);
    }
  }
  return result;
};
方法三:indexOf()
unique = (array) => {
  let result = [];
  array.forEach(item=>{
    if (result.indexOf(item) == -1) {
      result.push(item);
    }
  })
  return result;
};
方法三:includes()
unique = (array) => {
 let result = [];
  array.forEach(item=>{
  	if (!result.includes(item)) {
      result.push(item);
    }
  })
  return result;
};
方法四:filter()+indexOf()
unique = (array) => {
  let result = array.filter((item, index) => {
    return array.indexOf(item) == index;
  });
  return result;
};
方法五:reduce()+includes()
unique = (array) => {
 let result = array.reduce((prev, cur) => {
    return prev.includes(cur) ? prev : [...prev, cur];
  }, []);
  return result;
};

用js递归的方式写1到100求和

方法1
function add(n) {
  if (n==1) return 1;
  return add(n - 1) + n;
}
let total = add(100);
方法2
function sumFunc(num, start) {
  num += start;
  start++;
  if (start > 100) {
    console.log(num);
    return num;
  } else {
    sumFunc(num, start);
  }
}
sumFunc(0, 1);

写一个能遍历对象和数组的通用forEach函数

// forEach传入两个参数,obj,要执行的函数fn
function forEach(obj, fn) {
  var key;
  if (obj instanceof Array) {
    obj.forEach(function (item, index) {
      fn(index, item);
    });
  } else {
    for (key in obj) {
      if (obj.hasOwnProperty(key)) {
        fn(key, obj[key]);
      }
    }
  }
}
var arr = [1, 2, 3];
forEach(arr, function (index, item) {
  console.log(index, item);
});
var obj = {
  x: 100,
  y: 200,
};
forEach(obj, function (key, val) {
  console.log(key, val);
});

foreach、map与reduce的区别

foreach:让数组中的每一项做一件事

var arr=[1,2,3,4,5]
arr.foreach(function(item,index){
     console.log(item);
})

map:让数组通过某种计算产生一个新的数组

var arr=[1,2,3,4,5]
arr.map(function(item,index){
    return item*2;
})

reduce:让数组中的前项和后项做某种计算,并累计得到最终值。

var arr=[1,2,3,4,5];
var result=arr.reduce(function(prev,next){
   return prev+next;
})
console.log(result);

总结:
map 会返回新的数组
foreach 遍历数组中每一项
reduce 适合做累计运算
在这里插入图片描述

  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值