现在看来学习自定义某个内部Api不是重点,重点是掌握Api的原理。
曾经的面试被要求实现下面几个内部功能函数。
自定义JSON.stringify
function getType (vari) {
return Object.prototype.toString.call(vari).slice(8, -1)
}
function toStringify (obj) {
let str = ''
switch (getType(obj)) {
case 'Object': // 对象类型需要遍历递归
str += '{'
Object.keys(obj).forEach((k) => {
const res = toStringify(obj[k])
if (res) {
str += `"${k}":${res},`
}
})
return str.slice(0, -1) + '}'
case 'Array': // 数组类型需要遍历递归
str = '['
obj.forEach((k) => {
const res = toStringify(k)
if (res) {
str += res + ','
}
})
return str.slice(0, -1) + ']'
case 'Date': // 日期类型需要toJSON转日期字符串
return `"${obj.toJSON()}"`
case 'String': // 字符串需要加引号
return `"${obj}"`
case 'Number': // 数字类型直接返回
return `${obj}`
case 'Boolean': // 布尔类型true和false转字符串
if (obj === true) {
return 'true'
}
return 'false'
case 'RegExp': // 以下类型直接转空对象字符串
case 'Set':
case 'Map':
case 'WeakMap':
case 'WeakSet':
case 'ArrayBuffer':
case 'Blob':
case 'Int32Array':
case 'Int8Array':
case 'Int16Array':
return '{}'
case 'Null': // null转字符串
return 'null'
case 'BigInt': // bigint类型报错
throw Error('Do not know how to serialize a BigInt')
case 'Function': // 以下三个及其它类型返回空忽略
case 'Undefined':
case 'Symbol':
default:
}
}
当初被考到自定义实现JSON.stringify时候,一脸懵逼。面试肯定不用想没过就是了。这个东西就是将对象数据一下字符串化了。自己来实现的话就是根据输入的对象类型做针对性的处理。需要着重处理Object、Array、String、Date等类型。
当初对数据的理解还是不到位。数据本来就是可以相互转化的,也包括这种形式的转化。
自定义new操作符
// 自定义new操作符
function mynew(...args){
const constrctor = args.shift()
// 创建一个空对象obj,使这个空对象继承构造函数的prototype属性
const obj = Object.create(constrctor.prototype)
const result = constrctor.apply(obj, args)
return (typeof result === 'object' && result != null) ? result : obj
}
const test = function (name){
this.name = name
}
console.log(mynew(test, 'test'))
new操作符做了4件事:
- 创建一个空对象
- 继承原型属性
- 改变对象
this指向,执行构造函数 - 返回对象
如果构造函数有返回值,且返回的是对象且不为null则替换new操作符结果,否则就返回对象实例。
自定义数组map函数
Array.prototype.newMap = function(fn) {
var newArr = [];
for(var i = 0; i<this.length; i++){
newArr.push(fn(this[i],i,this))
}
return newArr;
}
自定义instanceof
// 自定义个instanceof 函数 困难
const myInstanceof = (Left, Right)=>{
if(typeof Left!=='object') {
return false
}
while(true) {
if(Left === null) {
return false
}
// 关键点1
if(Right.prototype === Left.__proto__) {
return true
}
// 关键点2
Left = Left.__proto__
}
}
console.log(myInstanceof({}, Object))
理解上面自定义instanceof就能理解原型链。关键点是__proto__可以一直回溯,一直到null为止。若__proto__上面有节点等于Right.prototype则Left是Right的实例。所以Right.prototype是__proto__上的一个节点。
一般情况下声明的对象字面量自带原型属性,如果想去掉原型属性可以用Object.create(null)。有段时间老是被问原型链的问题。
自定义bind函数
// 自定义bind
Function.prototype.mybind = Function.prototype.bind || function(context) {
var me = this
// 去除第一个元素
var args = Array.prototype.slice.call(arguments, 1)
var F= function(){}
F.prototype = this.prototype
var bound = function(){
// 类数组转为数组
var innerArgs = Array.prototype.slice.call(arguments)
var finalArgs = args.contact(innerArgs)
return me.apply(this instanceof F ? this : context || this, finalArgs)
}
bound.prototype = new F()
return bound;
}
const foo = {
value: 1
};
const test = function(){
console.log(this.value);
}
console.log(test.bind(foo)())
改变this指向的方法有apply、call,两者只是参数不同。apply第二个参数为数组,call为参数序列。还有bind。上面自定义考虑了如果bind返回的函数作为构造函数时的情况,这时new的优先级更高。
自定义apply函数
Function.prototype.applyFn = function (targetObject, argsArray){
if(typeof argsArray === 'undefined' || argsArray === null) {
argsArray = []
}
if(typeof targetObject === 'undefined' || targetObject === null) {
targetObject = window
}
targetObject = new Object(targetObject)
const targetFnKey = 'targetFnKey' // 此时要求原有对象没有该属性否则会被覆盖
targetObject[targetFnKey] = this // this指调用的函数
const result = targetObject[targetFnKey](...argsArray) // 隐式绑定 this作为targetObject内部函数调用时 this指向targetObject
delete targetObject[targetFnKey]
return result
}
上述自定义实现有个隐式绑定,也就是将函数作为对象的一个属性,之后调用函数时,其中的this自然指向对象。
学习自定义内部功能Api不光是为了更好地面试,也是为了加深对相应功能Api的理解。理解深入了自然能够运用的得心应手。本文只是抛砖引玉,其实还有别的面试可以考的自定义内部功能函数,比如数组的reduce函数,感兴趣的可以探索实现。