手写javascript原始方法
手写javascript原始方法
一、手写new方法
js new方法是实例化一个构造函数,作用如下
new
操作符会返回一个对象,所以我们需要在内部创建一个对象- 这个对象,也就是构造函数中的
this
,可以访问到挂载在this
上的任意属性 - 这个对象可以访问到构造函数原型上的属性,所以需要将对象与构造函数链接起来
- 返回原始值需要忽略,返回对象需要正常处理
代码如下:
Function.prototype.myNew = function(P) {
let o = {};
let arg = Array.prototype.slice.call(arguments, 1);//也可以用es6的解构语法将其他参数传入...args
o.__proto__ = P.prototype;//Object.setPrototypeOf(o,P.prototype)
P.prototype.constructor = P;
P.apply(o, arg);
return result instanceof Object ? result : o;
};
/*
参数是构造函数,并且调用argumens对象将后边的其他参数截取出来
然后内部创建一个空对象o
因为 o 对象需要访问到构造函数原型链上的属性.
将 o 绑定到构造函数上,并且传入剩余的参数
判断构造函数返回值是否为对象,如果为对象就使用构造函数返回的值,否则使用 obj,这样就实现了忽略构造函数返回的原始值
*/
//用法
function Person(name, age) {
this.name = name;
this.age = age;
// console.log(this);
this.show();
}
Person.prototype.show = () => {
console.log(111);
};
let p2 = Function.myNew(Person,'caotudou', 12)
二、call、apply方法
call和apply都是改变调用函数的this的方法,核心思想就是_让指定的object成为函数的调用者_,他都是Function原型上的方法,第一个参数this指向的对象,call方法后边的参数是依次传入,apply的只有两个参数第二个参数是一个数组。在编程中很常用;示例如下代码:
1、不用call和apply的情况
let obj={name:"xiaoming"}
let name="zhangsan"
function showName(){
return this.name
}
showName()//'' 这时候this指向的window,let定义的变量不会挂在到window上所以是空
如果要是想调用showName的时候输出'xiaoming',该怎么做,那么就用到了call或者apply方法,如下:
//call
showName.call(obj)//返回了'xiaoming'
//如果想要传入其他的参数,姿势应该是这个样子
showName.call(obj,1,2,3,4)
//apply方法
showName.apply(obj)//返回了'xiaoming'
//如果想要传入其他参数呢,姿势应该是这样
showName.apply(obj,[1,2,3,4])
以上列子我们看到了call和apply的使用方法,小板凳放下,继续听,我们主要目的还是手写这些方法的实现,因为大多手都不用造轮子,但是偶尔造一下轮子也是没问题的,有助于我们学习嘛;
先来总结下,调用call到底发生了什么
- 接收到传入的这个对象,如果为空就指向window对象,并且这时候的this指向的是调用的这个函数也就是上边列子中的showName;
- 在这个传入对象上创建一个唯一的函数并且将this赋值给它;
- 调用这个函数并且传入其他参数;删除该函数;
代码示例:
Function.prototype.myOwnCall = function(context) {
//生成唯一值
let uniqueID = (Math.random() + new Date().getTime()).toString(32).slice(0, 8)
while (context.hasOwnProperty(uniqueID)) {
uniqueID = (Math.random() + new Date().getTime()).toString(32).slice(0, 8);
}
//判断是否存在
if (context === null || context === undefined) {
// 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
context = window
} else {
context = Object(context) // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
}
//赋值this
context[uniqueID] = this;
//得到后边的参数
let arg = [...arguments].slice(1);
调用并将this传过去
let result=context[uniqueID](...arg)
delete context[uniqueID];
return result
};
//测试
showName.myOwnCall(obj,1,2,3)//xiaoming nice!
接下来是apply方法,其实就是参数哪里换一下就ok了
Function.prototype.myOwnApply = function(context, arr) {
// JavaScript权威指南判断是否为类数组对象
function isArrayLike(o) {
if (
o && // o不是null、undefined等
typeof o === 'object' && // o是对象
isFinite(o.length) && // o.length是有限数值
o.length >= 0 && // o.length为非负值
o.length === Math.floor(o.length) && // o.length是整数
o.length < 4294967296
)
// o.length < 2^32
return true;
else return false;
}
if (context === null || context === undefined) {
context = window; // 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
} else {
context = Object(context); // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
}
//生成唯一值
let uniqueID = (Math.random() + new Date().getTime()).toString(32).slice(0, 8);
while (context.hasOwnProperty(uniqueID)) {
uniqueID = (Math.random() + new Date().getTime()).toString(32).slice(0, 8);
}
context[uniqueID] = this; //给context添加一个方法 指向this
// 处理参数 去除第一个参数this 其它传入fn函数
let args = arguments[1];
let result = null;
if (!arr) {
result = context.fn(); //执行fn
} else {
if (!Array.isArray(args) && !isArrayLike(args)) {
throw new TypeError('myOwnAppay第二个参数不为数组并且不为类数组对象');
} else {
args = Array.from(args);
//console.log(args);
result = context[uniqueID](...args);
}
}
delete context.fn; //删除方法
return result;
};
//测试
showName.myOwnCall(obj,1,2,3)//xiaoming nice!
三、bind方法
最后两分钟来说说bind方法:
showName.bind(o)(1, 2, 3);
bind实际上是把对象穿进去之后,在内部定义一个变量保存了showName,
返回一个新的函数,第二次调用的时候执行的是这个函数,新的函数内部执行了第一步保存的原函数通过apply的方式将参数传入
Function.prototype.myOwnBind = function(context) {
if (typeof this !== 'function') {
throw new Error(this + "cannot be bound as it's not callable");
}
let arg = [...arguments].slice(1);
let boundTargetFunction = this;
return function boundFunction() {
let newArg = [...arguments];
// console.log(newArg);
return boundTargetFunction.apply(context, arg.concat(newArg));
};
};
bind方法也是改变函数的this指向,不同的是bind返回的是一个函数并不直接执行调用函数,bind方法实际上用了函数柯里化的技巧,将要绑定的对象obj先传过去,定义boundTargetFunction=this;这时候返回了一个自定义函数boundFunction,这个函数内的局部变量已经包含(闭包)了上一步用来指向_context_的对象与showName函数的变量boundTargetFunction,再次调用boundFunction的时候根据需要传入不同的参数 之后,执行的实际上是boundTargetFunction并且把要绑定的对象_context_,和第二次调用传入的其他参数用apply方法执行了;
可能写的不是很全面,如问题及时联系