前端常见面试手写代码总结

new 操作符

    function myNew(Func, ...args) {
      // 1. 创建一个新对象
      const obj = {}
      // 2. 新对象原型指向构造函数原型对象
      obj.__proto__ = Func.prototype
      // 3. 将构造函数的 this 指向新对象
      let result = Func.apply(obj, args)
      // 4. 根据返回值判断
      // 如果该函数没有返回对象,则返回this
      return result instanceof Object ? result : obj
    }

实现 call() 方法

    Function.prototype.myCall = function (context, ...args) {
      let cxt = context || window;
      //将当前被调用的方法定义在cxt.func上.(为了能以对象调用形式绑定this)
      //新建一个唯一的Symbol变量避免重复
      let func = Symbol()
      cxt[func] = this;
      args = args ? args : []
      //以对象调用形式调用func,此时this指向cxt 也就是传入的需要绑定的this指向
      const res = args.length > 0 ? cxt[func](...args) : cxt[func]();
      //删除该方法,不然会对传入对象造成污染(添加该方法)
      delete cxt[func];
      return res;
    }

实现 apply() 方法

    Function.prototyp.myApply = function (context, args = []) {
      let ctx = context || window
      let func = Symbol()
      ctx.func = this
      const res = args.length > 0 ? ctx.func(...args) : ctx.func()
      delete ctx.func
      return res
    }

实现 bind() 方法

  Function.prototype.bind = function (context) {
      // 调用 bind 的不是函数,需要抛出异常
      if (typeof this !== "function") {
        throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
      }

      // this 指向调用者
      var self = this;
      // 实现第2点,因为第1个参数是指定的this,所以只截取第1个之后的参数
      var args = Array.prototype.slice.call(arguments, 1);

      // 实现第3点,返回一个函数
      return function () {
        // 实现第4点,这时的arguments是指bind返回的函数传入的参数
        // 即 return function 的参数
        var bindArgs = Array.prototype.slice.call(arguments);
        // 实现第1点,改变返回函数的this指向
        return self.apply(context, args.concat(bindArgs));
      }
    }

但还有一个问题,bind 有以下一个特性:
一个绑定函数也能使用 new 操作符创建对象:这种行为就像把原函数当成构造器,提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。

寄生式组合继承

借用父构造函数继承父类属性、结果用原型对象继承父类型方法

      // 借用父构造函数继承属性
      // 1. 父构造函数
      function Father(uname, age) {
        // this 指向父构造函数的对象实例
        this.uname = uname;
        this.age = age;
      }
      Father.prototype.money = function () {
        console.log(100000);
      };
      // 2 .子构造函数
      function Son(uname, age, score) {
        // this 指向子构造函数的对象实例
        Father.call(this, uname, age);
        this.score = score;
      }
      // Son.prototype = Father.prototype;  这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化
      Son.prototype = new Father(); // Son.prototype = Object.create(Father.prototype)
      
      // 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数
      Son.prototype.constructor = Son;

ES6继承

class Person {
    constructor(name, age) {
        this.name = name
        this.age =  age
    }
    eat() {
        console.log(this.name + 'eat food!')
    }
}
class Woman extends Person {
    constructor(name, age, sex) {
        super(name, age)
        this.sex = sex
    }
    eat() {
        super.eat()
    }
}

instanceof的实现

instanceof 运算符用来检测 constructor.prototype是否存在于参数 object 的原型链上。

    function myInstanceof(a, b) {
      let right = b.prototype
      let left = a.__proto__
      while (true) {
        if (left == null) {
          return false
        }
        if (left == right) {
          return true
        }
        left = left.__proto__
      }
    }
// 上面方法的__proto__可能不兼容
function myInstanceof (left, right) {
	// 获取对象原型
	let proto = Object.getPrototypeOf(left)
	// 获取构造函数的prototype对象
	let prototype = right.prototype
	// 判断构造函数的prototype对象是否在对象的原型链上
	while (true) {
		if (!proto) return false
		if (proto === prototype) return true
		proto = Object.getPrototypeOf(proto)
	}
}

Object.create的实现

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

Object.assign的实现

// 剩余参数语法允许我们将一个不定数量的参数表示为一个数组
Object.myAssign = function (target, ...source) {
	if (target == null) {
		throw new TypeError('Cannot convert undefined or null to object')
	}
	let ret = Object(target)
	source.forEach(function (obj) {
		if (obj !== null) {
			for (let key in obj) {
				if (obj.hasOwnProperty(key)) {
					ret[key] = obj[key]
				}
			}
		}
	})
	return ret
}

Promise

Promise.all()的实现

    Promise.all = function(promiseArr) {
      let result = [], index = 0
      return new Promise((resolve, reject) => {
        promiseArr.forEach((p,i) => {
          Promise.resolve(p).then(val => {
            index++
            result[i] = val
            if (index === promiseArr.length) {
              resolve(result)
            }
          }, err => {
            reject(err)
          })
        })
      })
    }

Promise.race()的实现

Promise.race = function(promiseArr) {
	return new Promise((resolve, reject) => {
		promiseArr.forEach((p) => {
			Promise.resolve(p).then(val => {
				resolve(val)
			}, err => {
				reject(err)
			})
		})
	})
}

Ajax的实现

// 存储的是默认值
 var defaults = {
 	type: 'get',
 	url: '',
 	data: {},
 	header: {
 	'Content-Type: application/x-www-form-urlencoded'
 	}
 	success: function() {}
 	error: function() {}
 }
function ajax (url, method, body, headers) {
 returan new Promise(resolve, reject) {
 	let xhr = new XMLHttpRequest()
 	xhr.open(method, url)
 	// 设置请求头
 	for(let key in headers) {
   xhr.setRequestHeader(key, headers[key])
 	}
 	xhr.send(body)
 	xhr.onreadystatechange(() => {
   if (xhr.readyState === 4 && xhr.status === 200) {
   	resolve(xhr.responseText)
   } else {
   	reject(xhr.status)
   }
 	})
 }
}

防抖(debounce)

对于短时间内连续触发的事件(上面的滚动事件),防抖的含义就是让某个时间期限(如下面的1000毫秒)内,事件处理函数只执行一次。
在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。

    function debounce(fn, delay) {
      let timer = null
      // debounce 被调用的时候,this 此时指向 window
      console.log('debounce 里面的 this 指向' + this)
      return function () {
        // 匿名函数被调用的时候,this 指向 HTMLElement(此时指向document)
        console.log('返回的匿名函数里面的 this 指向' + this)
        if (timer) {
          clearTimeout(timer)
        }
        timer = setTimeout(fn, delay)
      }
    }
    function test() {
      // test被调用的时候,this 此时指向 window,应证了setTimeout 里面的回调函数的this指向window
      console.log('debounce', this);
    }
    // debounce 一开始就被调用了,并非是由 onmousemove 事件触发的,不要混淆了。我就混淆了,分析了好久,一天就没了呜呜~
    document.onmousemove = debounce(test, 3000)

节流(throttle)

我们可以设计一种类似控制阀门一样定期开放的函数,也就是让函数执行一次后,在某个时间段内暂时失效,过了这段时间后再重新激活(类似于技能冷却时间)。效果:如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效。实现 这里借助setTimeout来做一个简单的实现,加上一个状态位valid来表示当前函数是否处于工作状态:

    function throttle(fn, delay) {
      let valid = true
      return function () {
        if (!valid) {
          return false
        }
        valid = false
        setTimeout(() => {
          fn()
          valid = true
        }, delay)
      }
    }
    function test() {
      // test被调用的时候,this 此时指向 window,应证了setTimeout 里面的回调函数的this指向window
      console.log('throttle', this);
    }
    // throttle 一开始就被调用了,并非是由 onmousemove 事件触发的,不要混淆了。我就混淆了,分析了好久,一天就没了呜呜~
    document.onmousemove = throttle(test, 3000)

深拷贝(deepclone)

const target = {
    field1: 1,
    field2: undefined,
    field3: 'ConardLi',
    field4: {
        child: 'child',
        child2: {
            child2: 'child2'
        }
    }
};
target.target = target
function deepClone (obj, map = new WeakMap()) {
    if(obj instanceof RegExp) return new RegExp(obj)
    if(obj instanceof Date) return new Date(obj)
    if(obj === null || typeof obj !== 'object') return obj

    if(map.has(obj)) {
        return map.get(obj)
    }

    let cloneObj =  Array.isArray(obj) ? [] : {}
    map.set(obj, cloneObj)
    for (let key in obj) {
        if(obj.hasOwnProperty(key)) {
            cloneObj[key] = deepClone(obj[key],map)
        }
    }
    return cloneObj
}
const cloneTarget = deepClone(target)
console.log(cloneTarget)

数组扁平化(flat)

let arr = [1, 2, [3, 4, [5, [6]]]]
function fn(arr) {
return arr.reduce((pre, cur) => {
  return pre.concat(Array.isArray(cur) ? fn(cur) : cur)
}, [])
}
// [1, 2, 3, 4, 5, 6]
console.log(fn(arr))
function fn() {
    let ret = []
    return function flat(arr) {
        for(let item of arr) {
            if(item.constructor === Array) {
                ret.concat(flat(item))
            } else {
                ret.push(item)
            }
        }
        return ret
    }
}
const arr = [1, 2, 3, [4, 5], [6, [7, [8]]]]
console.log(fn()(arr));
function flat(arr) {
	var res = [];
	for (let item of arr) {
		if (Array.isArray(item)) {
			res = res.concat(flat(item));
		} else {
			res.push(item);
		}
	}
	return res
}
console.log(flat([1,2,[3,4],5,[6,[7,8]],9]));

函数柯里化

我们已经知道,在函数主体中,arguments数组的length属性指定了传递给该函数的实参数目。但是函数自身的length属性的含义却并非如此,它是只读特性,返回的是函数需要的实参的数目,也就是在函数的形参列表中声明的形参的数目。调用函数时可以传递给它任意数目的实参,函数能够从arguments数组中得到这些参数,而无需考虑它所声明的形参的数目。Function对象的length属性确切说明了一个函数声明的形参的个数。注意和arguments.length不同,这个length属性在函数体的内部和外部都有效。

function curry(func) {
	// func.length === 3 表示函数的形参列表中声明的形参的数目
	return function curried(...args) {
		if (args.length >= func.length) {
			// 下面的func.apply()里面的this指向window
			return func.apply(this, args);
		} else {
			return function (...args2) {
				// 下面直接使用curried(args.concat(args2))好像也没什么问题
				return curried.apply(this, args.concat(args2));
			}
		}
	};

}
function sum(a, b, c) {
	return a + b + c;
}
let curriedSum = curry(sum);
console.log(curriedSum(1, 2, 3));

实现sleep()

写法一

function sleep (time) {
    return new Promise((resolve, reject) => {
        setTimeout(() => resolve('shuchu'), time)
    })
}
sleep(3000).then(value => {
    console.log(value)
    console.log(3333);
})

写法二

function sleep (delay) {
    let start = (new Date()).getTime()
    while ((new Date()).getTime() - start < delay) {
        continue
    }
}
function test () {
    console.log('111');
    sleep(2000);
    console.log('222');
}
test()

图片懒加载

let num = document.getElementsByTagName('img').length
let img = document.getElementsByTagName('img')
let n = 0
lazyload()
window.onscroll = lazyload
function lazyload() {
	let seeHeight = document.documentElement.clientHeight
	let scrollTop = document.documentElement.scrollTop || document.body.scrollTop
	for(let i = n; i < num; i++) {
		if(img[i].offsetTop - scrollTop < seeHeight) {
			if(img[i].getAttribute('src') == './img2/timg.gif') {
				img[i].src = img[i].getAttribute('data-src')
			}
			n = i+1
		}
	}
}

在懒加载的实现中,有两个关键的数值:一个是当前可视区域的高度,另一个是元素距离可视区域顶部的高度。当前可视区域的高度,在现代浏览器及 IE9 以上的浏览器中,可以使用window.innerHeight属性获取,在低版本的 IE 中使用document.documentElment.clientHeight 获取,这里我们兼容两种情况:
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
而元素距离可视区域顶部的高度,这里我们用 getBoundingClientRect()方法来获取返回元素的大小和相对于尺寸的位置,对于该 API,MDN 的解释是:

Element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置。
返回的属性中有一个相对于可视区域顶部的高度也就是top属性,刚好就是我们需要的元素距离顶部的距离。

let imgList = [...document.querySelectorAll('img')]
let length = imgList.length
const imgLazyLoad = function () {
   let count = 0
   return (function () {
       console.log(length);
       let deleteIndexList = []
       imgList.forEach((img, index) => {
           // Element.getBoundingClientRect()返回元素的大小及其相对于视口的位置
           let rect = img.getBoundingClientRect()
           // 若图片进入了可视区
           if (rect.top < window.innerHeight) {
               img.src = img.dataset.src
               deleteIndexList.push(index)
               count++
           }
           if (count === length) {
               document.removeEventListener('scroll', imageLazyLoad)
           }
       })
       imgList = imgList.filter((img, index) => !deleteIndexList.includes(index))
       console.log(imgList);
   })()
}
document.addEventListener('scroll', imgLazyLoad)

URL参数解析

function parseParam(url,paramkey) {
  // const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来
  const paramsArr = url.split('?')[1].split('#')[0].split('&'); // 将字符串以 & 分割后存到数组中
  let paramsObj = {};
  // 将 params 存到对象中
  paramsArr.forEach(param => {
    if (/=/.test(param)) { // 处理有 value 的参数
      let [key, val] = param.split('='); // 分割 key 和 value
      val = decodeURIComponent(val); // 解码
      val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字

      if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值
        paramsObj[key] = [].concat(paramsObj[key], val);
      } else { // 如果对象没有这个 key,创建 key 并设置值
        paramsObj[key] = val;
      }
    } else { // 处理没有 value 的参数
      paramsObj[param] = true;
    }
  })
  // return paramsObj.hasOwnProperty(paramkey) ? paramsObj[paramkey] : false;
  return paramsObj
}
let res = parseParam('https://m.xiaohongshu.com?name=1&age=18#test1=xiaoming1', 'age')
console.log(res);

冒泡排序

    function mSort(arr) {
      let n = arr.length
      for(let i = 0; i < n-1; i++) {
        for(let j = 0; j < n-1-i; j++) {
          if(arr[j] > arr[j+1]) {
            let temp = arr[j]
            arr[j] = arr[j+1]
            arr[j+1] = temp
          }
        }
      }
      return arr
    }
    let arr = [7,6,4,5,3,1,2]
    console.log(arr);
    console.log(mSort(arr));

选择排序

    function sSort(arr) {
      let n = arr.length
      for(let i = 0; i < n; i++) {
        let min = i
        for(let j = i + 1; j < n; j++) {
          if(arr[j] < arr[min]) {
            min = j
          }
        }
        let temp = arr[i]
        arr[i] = arr[min]
        arr[min] = temp
      }
      return arr
    }
    let arr = [7,6,4,5,3,1,2]
    console.log(arr);
    console.log(sSort(arr));

快速排序

		// 交换数组中子数组的记录,使枢轴记录到位,并返回其所在位置
		// 此时在它之前(后)的记录均不大(小)于它
		function Partition(nums, low, high) {
			// 用子数组的第一个记录作为枢轴记录
			let pivotkey = nums[low]
			// 从数组的两端交替向中间扫描
			while(low < high) {
				while(low < high && pivotkey <= nums[high]) {
					high--
				}
				// 将比枢轴记录小的记录交换到低端
				let temp = nums[high]
				nums[high] = nums[low]
				nums[low] = temp

				while(low < high && nums[low] <= pivotkey) {
					low++
				}
				// 将比枢轴记录大的记录交换到高端
				temp = nums[low]
				nums[low] = nums[high]
				nums[high] = temp
			}
			// 返回枢轴所在位置
			return low
		}
		// 对数组中的子序列[low,low+1,...,high]做快速排序
		function QSort(nums, low, high) {
			if(low < high) {
				// 将[low,...,high]一分为二,计算出枢轴值pivot
				let pivot = Partition(nums, low, high)
				QSort(nums, low, pivot-1)
				QSort(nums, pivot+1, high)
			}
		}
		let arr = [7,6,4,5,3,1,2]
		QSort(arr, 0, arr.length - 1)
		console.log(arr);

归并排序

以下是归并排序的步骤:
将给定的列表分为两半(如果列表中的元素数为奇数,则使其大致相等)。
以相同的方式继续划分子数组,直到只剩下单个元素数组。
从单个元素数组开始,合并子数组,以便对每个合并的子数组进行排序。
重复第 3 步单元,直到最后得到一个排好序的数组。

		function merge(left, right) {
			const arr = []
			while (left.length && right.length) {
				if (left[0] < right[0]) {
					arr.push(left.shift())
				} else {
					arr.push(right.shift())
				}
			}
			return [...arr, ...left, ...right]
		}
		function mSort(arr) {
			if (arr.length < 2) {
				return arr
			}
			let half = Math.floor(arr.length / 2)
			const left = arr.splice(0, half)
			return merge(mSort(left), mSort(arr))
		}
		console.log(mSort([7, 6, 4, 5, 3, 1, 2]));

插入排序

		function iSort(arr) {
			var len = arr.length
			var preIndex, current
			for(let i = 1; i < len; i++) {
				preIndex = i-1
				current = arr[i]
				while(preIndex >= 0 && arr[preIndex] > current) {
					arr[preIndex+1] = arr[preIndex]
					preIndex--
				}
				arr[preIndex+1] = current
			}
			return arr
		}
		console.log(iSort([7,6,4,5,3,1,2]))

希尔排序

    function shellSort(arr) {
      let n = arr.length
      for(let gap = Math.floor(n/2); gap > 0; gap = Math.floor(gap/2)) {
        for(let i = gap; i < n; i++) {
          let j = i
          let current = arr[i]
          while(j-gap >= 0 && current < arr[j-gap]) {
            arr[j] = arr[j-gap]
            j = j - gap
          }
          arr[j] = current
        }
      }
      return arr
    }
    console.log(shellSort([7,6,4,5,3,1,2]));
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值