20 道常考手写题(上)
1. call
function.call(thisArg, arg1, arg2, ...)
thisArg
可选的。在 function 函数运行时使用的 this 值。请注意,this 可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。
arg1, arg2, ...
指定的参数列表。
返回值
使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined。
具体实现
Function.prototype._call = function (ctx, ...args) {
// 如果为严格模式this为 undefined , node 为 global , 浏览器为windows
const _this = (function () {
return this;
})();
ctx = ctx == null ? _this : Object(ctx); // 原始值会被包装
// 注: 为防止_fn重名和隐藏该属性可以用Symbol
const key = Symbol("fn");
ctx[key] = this;
const res = ctx[key](...args);
delete ctx[key];
return res;
};
Object
// Object 构造函数为给定的参数创建一个包装类对象(object wrapper),具体有以下情况:
// 如果给定值是 null 或 undefined,将会创建并返回一个空对象 // 如果传进去的是一个基本类型的值,则会构造其包装类型的对象 // 如果传进去的是引用类型的值,仍然会返回这个值,经他们复制的变量保有和源对象相同的引用地址
2. apply
apply 和 call 的区别为第二个参数
func.apply(thisArg, [argsArray])
thisArg
必选的。在 func 函数运行时使用的 this 值。请注意,this 可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。
argsArray
可选的。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 null 或 undefined,则表示不需要传入任何参数。从 ECMAScript 5 开始可以使用类数组对象。浏览器兼容性 请参阅本文底部内容。
返回值
调用有指定 this 值和参数的函数的结果。
// 注意 args = []要设置默认,否则 ...args 会报错
Function.prototype._apply = function (ctx, args = []) {
// 如果为严格模式this为 undefined , node 为 global , 浏览器为windows
const _this = (function () {
return this;
})();
ctx = ctx == null ? _this : Object(ctx); // 原始值会被包装
// 注: 为防止_fn重名和隐藏该属性可以用Symbol
const key = Symbol("fn");
ctx[key] = this;
const res = ctx[key](...args);
delete ctx[key];
return res;
};
3. bind
注意点:
- 如果使用
new
运算符构造绑定函数,则忽略该值 bind
可以传预设参数- 或者 thisArg 是 null 或 undefined,执行作用域的 this 将被视为新函数的 thisArg。
Function.prototype._bind = function (ctx, ...rest) {
const fn = this;
// 获取当前环境的this
const _this = (function () {
return this;
})();
ctx = ctx == null ? _this : Object(ctx);
return function (...args) {
// 如果new 则 bind 失效
if (this instanceof fn) {
return new fn(...rest, ...args);
}
return fn.apply(ctx, [...rest, ...args]);
};
};
4. inherit
- ES6
extend
继承
class Person {}
class Student extends Person {}
- 通过函数原型继承
function inherit(Child, Parent) {
Child.prototype = Object.create(Parent.prototype, {
constructor: {
value: Child,
enumerable: false, // 不可枚举该属性
writable: true, // 可改写该属性
configurable: true, // 可用 delete 删除该属性
},
});
// 继承静态方法
Object.setPrototypeOf(Sub, Super);
}
5. new
注意点:
- generator 和 箭头函数不能
new
new
返回值如果为对象则返回该对象,否则返回new
创建的实例
function _new(Fn, ...args) {
// 判断参数,必须为函数
if (typeof Fn !== "function") {
throw new TypeError("parameter type error");
}
//且不能为箭头函数和 generator
if (!Fn.hasOwnProperty("prototype") || Object.prototype.toString.call(Fn) === "[object GeneratorFunction]") {
throw new Error(`${Fn.name} is not a constructor`);
}
// 创建一个对象隐式原型指向Fn的显示原型
const obj = Object.create(Fn.prototype);
// 调用执行
const res = Fn.apply(obj, args);
//判断返回值
return res instanceof Object ? res : obj;
}
6. instanceof
注意点:
左边如果为原始值直接返回 false,左边为 null 也返回 false
function _instanceof(l, R) {
// 判断是不是对象或者函数(不包括null)
const isObjectOrFunction = (target) => {
return (typeof target === "object" && target !== null) || typeof target === "function";
};
if (!isObjectOrFunction(l)) {
//原始值,直接返回false
return false;
} else {
// 创建一个指针,指向l的隐式原型
let p = l;
// 构造函数的原型
const prototype = R.prototype;
while (p) {
let proto = p.__proto__; //或者使用 Object.getPrototypeOf
// 如果隐式原型===显示原型,则返回true
if (proto === prototype) {
return true;
}
// 沿着原型链执行,最终到null结束
p = proto;
}
// 执行了到原型链终点,没有找到则返回false
return false;
}
}
console.log(_instanceof(1, Number)); //false
console.log(_instanceof("1", String)); //false
console.log(_instanceof(null, Object)); //false
console.log(_instanceof({}, Object)); //true
console.log(_instanceof(() => {}, Object)); //true
console.log(_instanceof(new Date(), Date)); //true
console.log(_instanceof(new Date(), Object)); //true
// 三个有意思的
console.log(_instanceof(Function, Function)); //true
console.log(_instanceof(Object, Function)); //true
console.log(_instanceof(Function, Object)); //true
7. deepClone
要求
- 支持对象、数组、日期、正则的拷贝。
- 处理原始类型(原始类型直接返回,只有引用类型才有深拷贝这个概念)。
- 处理 Symbol 作为键名的情况。
- 处理函数(函数直接返回,拷贝函数没有意义,两个对象使用内存中同一个地址的函数,没有任何问题)。
- 处理 DOM 元素(DOM 元素直接返回,拷贝 DOM 元素没有意义,都是指向页面中同一个)。
- 额外开辟一个储存空间 WeakMap,解决循环引用递归爆栈问题(引入 WeakMap 的另一个意义,配合垃圾回收机制,防止内存泄漏)
const a = {
str: "abc",
arr: [1, 2, 3, { value: 4 }],
obj: {
name: "b",
},
date: new Date(),
reg: new RegExp(),
null: null,
[Symbol("name")]: "symbol",
fn: () => {},
};
a.a = a; //循环引用
const b = deepClone(a);
console.log(a);
console.log(b);
function deepClone(target, hash = new WeakMap()) {
// 处理原始类型和函数 不需要深拷贝,直接返回
if (typeof target !== "object" || target == null) return target;
//处理Date
if (target instanceof Date) return new Date(target);
// 处理 RegExp
if (target instanceof RegExp) return new RegExp(target);
// 处理 HTMLElement
if (target instanceof HTMLElement) return target;
if (hash.has(target)) {
return hash.get(target);
}
const newTarget = new target.constructor(); // 创建一个新的克隆对象或克隆数组
hash.set(target, newTarget);
// 处理Symbol为键的情况, Reflect.ownKeys遍历自身所有属性包括symbol
Reflect.ownKeys(target).forEach((key) => {
newTarget[key] = deepClone(target[key], hash); //hash 记得要传!!!
});
return newTarget;
}
8. compose
function compose(...funcs) {
if (funcs.length === 0) {
return (arg) => arg;
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce(
(a, b) =>
(...args) =>
a(b(...args))
);
}
9. currying
currying 函数柯里化
function currying(fn) {
const _args = [];
const len = fn.length;
return function _currying(...args) {
_args.push(...args);
if (_args.length >= len) {
return fn.apply(this, _args);
} else {
return _currying;
}
};
}
用柯里化更简洁直观的调用
function checkByRegExp(reg, string) {
return reg.test(string);
}
console.log(checkByRegExp(/^1\d{10}$/, "17622222222")); // 校验电话号码
console.log(checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, "test@163.com")); // 校验邮箱
//柯里化
const checkPhone = currying(checkByRegExp)(/^1\d{10}$/);
const checkEmail = currying(checkByRegExp)(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/);
console.log(checkPhone("17622222222"));
console.log(checkPhone("17611111111"));
console.log(checkPhone("test@163.com"));
10. flat
flat:
打平数组,比如 [1,[2,3,[4,5],[6]]] 转化为 [ 1, 2, 3, 4, 5, 6 ]
function flat(arr) {
return arr.reduce((memo, cur) => {
return Array.isArray(cur) ? [...memo, ...flat(cur)] : [...memo, cur];
}, []);
}
console.log(flat([1, [2, 3, [4, 5], [6]]])); //[ 1, 2, 3, 4, 5, 6 ]