实现 JSON.stringify
JSON.stringify(value[, replacer[, space]])MDN
基础知识点:
- 数组存放的是基本数据类型(
string,number,boolean)toString后返回的是数组里面的值,如[1,2,'3', "a", 'b'].toString() ==> 1,2,3,a,b - 不可枚举的属性会被忽略
代码解析点:
- 值的类型是
function,undefined的直接会返回undefined - 值的类型是
string需要添加"" - 值的类型是对象,对象里面属性的值为
function,undefined会直接过滤,不做转化 - 值的类型是对象,对象里面属性的值为
string需要添加"" - 值的类型是对象,对象里面属性的值为
object对象后需要递归调用转化方法 - 值的类型是数组,返回的要包含
[] - 值的类型是对象,返回的要包含
{}
function jsonToString(obj) {
const _objType = typeof obj;
if(_objType !== 'object') {
if(/function|undefined/.test(_objType)) {
return undefined;
} else if(/string/.test(_objType)) {
obj = '"'+ obj +'"';
}
return String(obj);
} else {
let v = '',
json = [],
isArr = Array.isArray(obj);
for(let k in obj) {
v = obj[k];
let _vType = typeof v;
if(/function|undefined/.test(_vType)) {
continue;
} else if(/string/.test(_vType)) {
v = '"'+ v +'"';
} else if(/object/.test(_vType)) {
jsonToString(v);
}
json.push((isArr ? '' : ('"'+ k +'":')) + String(v));
}
return (isArr ? '[': '{') + String(json) + (isArr ? ']' : '}');
}
}
实现 call | apply
function.call(thisArg, arg1, arg2, ...)MDN
基础知识点:
Array.from可以把类数组转化为数组call和apply的区分是call的参数是一个一个传过去的apply参数传的是数组(巧记:call和apply,apply第一个参数是a, 是数组array的第一个字母 ,所以apply传数组)
代码解析点:
call方法第一个参数传的是null, 相当于是传了window对象call方法传的不是对象(基本数据类型),会默认设置为一个 空{}call方法里面的this是调用方法的本身,this是一个方法(typeof this === 'function')
Function.prototype.call2 = function() {
let _this = arguments[0] || window;
if(typeof _this !== 'object') {
_this = {};
}
let param = Array.from(arguments).slice(1);
_this.fn = this;
let result = _this.fn(...param);
delete _this.fn;
return result;
}
Function.prototype.apply2 = function() {
let _this = arguments[0] || window;
let param = arguments[1] ? arguments[1] : [];
_this.fn = this;
let result = _this.fn(...param)
delete _this.fn;
return result;
}
实现 bind
function.bind(thisArg[, arg1[, arg2[, ...]]])MDN
会创建一个新函数。当这个新函数被调用时,
bind()的第一个参数将作为它运行时的this,之后的一序列参数将会在传递的实参前传入作为它的参数。(来自于MDN)
基础知识点:
bind方法第一个参数是绑定方法里面的this指向bind方法返回是一个方法bind方法返回的方法的参数也可以作为参数传递给原方法
代码解析点:
prototype原型需要注意
Function.prototype.bind2 = function() {
let content = arguments[0] || null;
let _fn = this;
let params = Array.from(arguments).slice(1);
let resFn = function() {
params = params.concat(Array.from(arguments));
_fn.apply(content, params);
}
resFn.prototype = this.prototype;
return resFn;
}
实现 new
new constructor[([arguments])]MDN
new使用构造函数创建实例对象,为实例对象添加this属性和方法
基础知识点
Array.prototype.shift.call(arguments)让类数组可以使用上数组的方法shift
代码解析点
- 新方法第一个参数必须为方法
new操作符返回一个对象- 原
fn方法返回对象才取fn的返回值,默认取新创建的对象 - 新对象
__proto__指向原函数prototype
function newF() {
// 获取 newF 方法的第一个参数
const fn = Array.prototype.shift.call(arguments);
// 如果 newF 方法第一个参数不为方法,就抛出异常
if(typeof fn !== 'function') {
throw fn + '方法第一个参数必须为方法';
}
// 获取 newF 方法传过去的其他参数,返回放在数组中
const params = Array.prototype.slice.call(arguments, 0);
// 创建一个新对象
const res = {};
// 新对象的 __proto__ 赋值于 fn.prototype
res.__proto__ = fn.prototype;
// 用apply调用传入的fn方法
const ret = fn.apply(res, params)
// 如果原方法有返回且返回的为对象,就把这个对象返回,否者返回新创建的对象
return typeof ret === 'object' ? ret : res;
}
实现 javascript 重载
所谓重载(overload),就是函数名称一样,但是随着传入的参数个数不一样,调用的逻辑或返回的结果会不一样
重载案例是闭包的经典应用
基础知识点
- 闭包(可以访问函数内部变量的函数就是闭包)
- 递归(不断的循环的调用自身)
代码解析点 (看注释代码解析)
(() => {
//IIFE+箭头函数,把要写的代码包起来,避免影响外界,这是个好习惯
// 当函数成为对象的一个属性的时候,可以称之为该对象的方法。
/**
* object:一个对象,以便接下来给这个对象添加重载的函数(方法)
* name:object被重载的函数(方法)名
* fn:被添加进object参与重载的函数逻辑
* 闭包
*/
function overload(object, name, fn) {
var oldMethod = object[name];//存放旧函数,本办法灵魂所在,将多个fn串联起来
object[name] = function() {
// 代码解析
// 本例object[name方法第一次调用的时候,arguments长度为0,fn这个函数是 fn2 有两个参数, 不匹配
// 走 else if 判断 oldMethod 是一个方法, oldMethod 方法有等于 object[name] 这个方法
// 再次调用 object[name] 这个方法,但是是 object[name] 方法的上一次赋值的方法
// 上一次的 fn 是 fn1 。而 fn1的参数为1,arguments 长度为0 不相等 就依次 走 else if
// 递归反复 oldMethod 是第一次 overload 调用的时候, fn 就等于 fn0 了
// fn0的参数为0,arguments 长度为0 相等。就只掉 调用 fn.apply 传入参数执行了
// fn.length为fn定义时的参数个数,arguments.length为重载方法被调用时的参数个数
if (fn.length === arguments.length) {//若参数个数匹配上
return fn.apply(this, arguments);//就调用指定的函数fn
} else if (typeof oldMethod === "function") {//若参数个数不匹配
return oldMethod.apply(this, arguments);//就调旧函数 注意:当多次调用overload()时,旧函数中又有旧函数,层层嵌套,递归地执行if..else 判断,直到找到参数个数匹配的fn
}
};
}
// 不传参数时
function fn0() {
return "no param";
}
// 传1个参数
function fn1(param1) {
return "1 param:" + param1;
}
// 传两个参数时,返回param1和param2都匹配的name
function fn2(param1, param2) {
return "2 param:" + [param1, param2];
}
let obj = {};//定义一个对象,以便接下来给它的方法进行重载
overload(obj, "fn", fn0);//给obj添加第1个重载的函数
overload(obj, "fn", fn1);//给obj添加第2个重载的函数
overload(obj, "fn", fn2);//给obj添加第3个重载的函数
console.log(obj.fn());//>> no param
console.log(obj.fn(1));//>> 1 param:1
console.log(obj.fn(1, 2));//>> 2 param:1,2
})();