跳至主要內容

手写javascript原始方法

Mr.Cao...大约 5 分钟

手写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到底发生了什么

  1. 接收到传入的这个对象,如果为空就指向window对象,并且这时候的this指向的是调用的这个函数也就是上边列子中的showName;
  2. 在这个传入对象上创建一个唯一的函数并且将this赋值给它;
  3. 调用这个函数并且传入其他参数;删除该函数;

代码示例:

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方法执行了;
可能写的不是很全面,如问题及时联系

上次编辑于:
贡献者: Caofangshuai
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.8