实现一个call
call做了什么:
- 将函数设为对象的属性
- 执行&删除这个函数
- 指定this到函数并传入给定参数执行函数
- 如果不传入参数,默认指向为 window
// 模拟 call bar.mycall(null);
//实现一个call方法:
Function.prototype.myCall = function(context) {
//此处没有考虑context非object情况
context.fn = this;
let args = [];
for (let i = 1, len = arguments.length; i < len; i++) {
args.push(arguments[i]);
}
context.fn(...args);
let result = context.fn(...args);
delete context.fn;
return result;
};
手写常见排序

冒泡排序
冒泡排序的原理如下,从第一个元素开始,把当前元素和下一个索引元素进行比较。如果当前元素大,那么就交换位置,重复操作直到比较到最后一个元素,那么此时最后一个元素就是该数组中最大的数。下一轮重复以上操作,但是此时最后一个元素已经是最大数了,所以不需要再比较最后一个元素,只需要比较到
length - 1的位置。
function bubbleSort(list) {
var n = list.length;
if (!n) return [];
for (var i = 0; i < n; i++) {
// 注意这里需要 n - i - 1
for (var j = 0; j < n - i - 1; j++) {
if (list[j] > list[j + 1]) {
var temp = list[j + 1];
list[j + 1] = list[j];
list[j] = temp;
}
}
}
return list;
}
快速排序
快排的原理如下。随机选取一个数组中的值作为基准值,从左至右取值与基准值对比大小。比基准值小的放数组左边,大的放右边,对比完成后将基准值和第一个比基准值大的值交换位置。然后将数组以基准值的位置分为两部分,继续递归以上操作
ffunction quickSort(arr) {
if (arr.length<=1){
return arr;
}
var baseIndex = Math.floor(arr.length/2);//向下取整,选取基准点
var base = arr.splice(baseIndex,1)[0];//取出基准点的值,
// splice 通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。
// slice方法返回一个新的数组对象,不会更改原数组
//这里不能直接base=arr[baseIndex],因为base代表的每次都删除的那个数
var left=[];
var right=[];
for (var i = 0; i<arr.length; i++){
// 这里的length是变化的,因为splice会改变原数组。
if (arr[i] < base){
left.push(arr[i]);//比基准点小的放在左边数组,
}
}else{
right.push(arr[i]);//比基准点大的放在右边数组,
}
return quickSort(left).concat([base],quickSort(right));
}
选择排序
function selectSort(arr) {
// 缓存数组长度
const len = arr.length;
// 定义 minIndex,缓存当前区间最小值的索引,注意是索引
let minIndex;
// i 是当前排序区间的起点
for (let i = 0; i < len - 1; i++) {
// 初始化 minIndex 为当前区间第一个元素
minIndex = i;
// i、j分别定义当前区间的上下界,i是左边界,j是右边界
for (let j = i; j < len; j++) {
// 若 j 处的数据项比当前最小值还要小,则更新最小值索引为 j
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
// 如果 minIndex 对应元素不是目前的头部元素,则交换两者
if (minIndex !== i) {
[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
}
}
return arr;
}
// console.log(selectSort([3, 6, 2, 4, 1]));
插入排序
function insertSort(arr) {
for (let i = 1; i < arr.length; i++) {
let j = i;
let target = arr[j];
while (j > 0 && arr[j - 1] > target) {
arr[j] = arr[j - 1];
j--;
}
arr[j] = target;
}
return arr;
}
// console.log(insertSort([3, 6, 2, 4, 1]));
实现一个JS函数柯里化
预先处理的思想,利用闭包的机制
- 柯里化的定义:接收一部分参数,返回一个函数接收剩余参数,接收足够参数后,执行原函数
- 函数柯里化的主要作用和特点就是
参数复用、提前返回和延迟执行
- 柯里化把多次传入的参数合并,柯里化是一个高阶函数
- 每次都返回一个新函数
- 每次入参都是一个
当柯里化函数接收到足够参数后,就会执行原函数,如何去确定何时达到足够的参数呢?
有两种思路:
- 通过函数的
length属性,获取函数的形参个数,形参的个数就是所需的参数个数 - 在调用柯里化工具函数时,手动指定所需的参数个数
将这两点结合一下,实现一个简单 curry 函数
通用版
// 写法1
function curry(fn, args) {
var length = fn.length;
var args = args || [];
return function(){
newArgs = args.concat(Array.prototype.slice.call(arguments));
if (newArgs.length < length) {
return curry.call(this,fn,newArgs);
}else{
return fn.apply(this,newArgs);
}
}
}
// 写法2
// 分批传入参数
// redux 源码的compose也是用了类似柯里化的操作
const curry = (fn, arr = []) => {
// arr就是我们要收集每次调用时传入的参数
let len = fn.length; // 函数的长度,就是参数的个数
return function(...args) {
let newArgs = [...arr, ...args] // 收集每次传入的参数
// 如果传入的参数个数等于我们指定的函数参数个数,就执行指定的真正函数
if(newArgs.length === len) {
return fn(...newArgs)
} else {
// 递归收集参数
return curry(fn, newArgs)
}
}
}
// 测试
function multiFn(a, b, c) {
return a * b * c;
}
var multi = curry(multiFn);
multi(2)(3)(4);
multi(2,3,4);
multi(2)(3,4);
multi(2,3)(4)
ES6写法
const curry = (fn, arr = []) => (...args) => (
arg => arg.length === fn.length
? fn(...arg)
: curry(fn, arg)
)([...arr, ...args])
// 测试
let curryTest=curry((a,b,c,d)=>a+b+c+d)
curryTest(1,2,3)(4) //返回10
curryTest(1,2)(4)(3) //返回10
curryTest(1,2)(3,4) //返回10
// 柯里化求值
// 指定的函数
function sum(a,b,c,d,e) {
return a + b + c + d + e
}
// 传入指定的函数,执行一次
let newSum = curry(sum)
// 柯里化 每次入参都是一个参数
newSum(1)(2)(3)(4)(5)
// 偏函数
newSum(1)(2)(3,4,5)
// 柯里化简单应用
// 判断类型,参数多少个,就执行多少次收集
function isType(type, val) {
return Object.prototype.toString.call(val) === `[object ${
type}]`
}
let newType = curry(isType)
// 相当于把函数参数一个个传了,把第一次先缓存起来
let isString = newType('String')
let isNumber = newType('Number')
isString('hello world')
isNumber(999)
实现bind方法
bind的实现对比其他两个函数略微地复杂了一点,涉及到参数合并(类似函数柯里化),因为bind需要返回一个函数,需要判断一些边界问题,以下是bind的实现
bind返回了一个函数,对于函数来说有两种方式调用,一种是直接调用,一种是通过new的方式,我们先来说直接调用的方式- 对于直接调用来说,这里选择了
apply的方式实现,但是对于参数需要注意以下情况:因为bind可以实现类似这样的代码f.bind(obj, 1)(2),所以我们需要将两边的参数拼接起来 - 最后来说通过
new的方式,对于new的情况来说,不会被任何方式改变this,所以对于这种情况我们需要忽略传入的this
简洁版本
- 对于普通函数,绑定
this指向 - 对于构造函数,要保证原函数的原型对象上的属性不能丢失
Function.prototype.myBind = function(context = window, ...args) {
// this表示调用bind的函数
let self = this;
//返回了一个函数,...innerArgs为实际调用时传入的参数
let fBound = function(...innerArgs) {
//this instanceof fBound为true表示构造函数的情况。如new func.bind(obj)
// 当作为构造函数时,this 指向实例,此时 this instanceof fBound 结果为 true,可以让实例获得来自绑定函数的值
// 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
return self.apply(
this instanceof fBound ? this : context,
args.concat(innerArgs)
);
}
// 如果绑定的是构造函数,那么需要继承构造函数原型属性和方法:保证原函数的原型对象上的属性不丢失
// 实现继承的方式: 使用Object.create
fBound.prototype = Object.create(this.prototype);
return fBound;
}
// 测试用例
function Person(name, age) {
console.log('Person name:', name);
console.log('Person age:', age);
console.log('Person this:', this); // 构造函数this指向实例对象
}
// 构造函数原型的方法
Person.prototype.say = function() {
console.log('person say');
}
// 普通函数
function normalFun(name, age) {
console.log('普通函数 name:', name);
console.log('普通函数 age:', age);
console.log('普通函数 this:', this); // 普通函数this指向绑定bind的第一个参数 也就是例子中的obj
}
var obj = {
name: 'poetries',
age: 18
}
// 先测试作为构造函数调用
var bindFun = Person.myBind(obj, 'poetry1') // undefined
var a = new bindFun(10) // Person name: poetry1、Person age: 10、Person this: fBound {}
a.say() // person say
// 再测试作为普通函数调用
var bindNormalFun = normalFun.myBind(obj, 'poetry2') // undefined
bindNormalFun(12) // 普通函数name: poetry2 普通函数 age: 12 普通函数 this: {name: 'poetries', age: 18}
注意:
bind之后不能再次修改this的指向,bind多次后执行,函数this还是指向第一次bind的对象
Promise实现
基于Promise封装Ajax
- 返回一个新的
Promise实例 - 创建
HMLHttpRequest异步对象 - 调用
open方法,打开url,与服务器建立链接(发送前的一些处理) - 监听
Ajax状态信息 - 如果
xhr.readyState == 4(表示服务器响应完成,可以获取使用服务器的响应了)xhr.status == 200,返回resolve状态xhr.status == 404,返回reject状态
xhr.readyState !== 4,把请求主体的信息基于send发送给服务器

这篇博客详细介绍了前端面试中常见的手写面试题,包括实现call方法、各种排序算法(冒泡排序、快速排序等)、JS函数柯里化、bind方法、Object.freeze、双向绑定、深拷贝、async/await、Node的require方法、JSONP、ES6的extends等。此外,还探讨了Promise实现、Array.of、reduce用法、版本号排序、map方法、JSON.parse等JavaScript核心概念和技巧。
最低0.47元/天 解锁文章
893

被折叠的 条评论
为什么被折叠?



