简介
为了巩固一下身为前端开发小菜鸟必备的脸面,从而让脸皮更加厚实一些,在这条道路活的更加滋润一些,故此开始整理一些JavaScript(脸面)使用的一些基础细节。
欢迎各位大牛莅临指导,从而让我在加厚脸皮的道路上走的更远,从而可以用脸皮挡子弹,挡导弹,挡核弹,然后无敌。哈哈哈!
今天探究一下new、apply、call、bind方法底层逻辑的实现。
new
new运算符,主要用来创建一个用户定义的类型对象的实例或者具有构造函数的内置对象的实例。
-
使用
-
使用过程
// 第一种情况,无指定返回,默认返回undefined class Person { constructor(name = 'niu') { this.name = name; } } const boy = new Person(); console.log(boy) // 第二种情况,返回一个普通的对象 class Alien { constructor(name = 'jiao') { this.name = name; return { age: 18, sex: 'girl' } } } const girl = new Alien(); console.log(girl) // 第三种情况,返回基本数据类型 class AlienFirst { constructor(name = 'jiao') { this.name = name; return 123 && "‘123’" && true && 10n && null } } const girlFirst = new AlienFirst(); console.log(girlFirst) // 第四种情况,返回数组对象 class AlienSecond { constructor(name = 'jiao') { this.name = name; return [1,2,3,4,5] } } const girlSecond = new AlienSecond(); console.log(girlSecond) // 第五种情况,返回日期对象 class AlienThird { constructor(name = 'jiao') { this.name = name; return new Date() } } const girlThird = new AlienThird(); console.log(girlThird) // 第六种情况,返回函数对象 class AlienFourth { constructor(name = 'jiao') { this.name = name; function test () {} return test } } const girlFourth = new AlienFourth(); console.log(girlFourth)
-
使用结果
new关键词执行之后总是会返回一个对象,要么是实例对象,要么是构造函数return语句指定的对象。
-
-
实现
-
实现逻辑
- 创建一个新的对象;
- 将构造函数的作用域赋给新对象(this指向新对象);
- 执行构造函数中的代码(为这个对象添加新属性);
- 返回新对象。
-
预期结果
- 实例对象可以访问到构造函数私有属性;
- 实例对象可以访问到构造函数的原型所在的原型链;
- 构造函数返回的最终结果是引用数据类型。
-
代码实现
// new操作符实现 function AcieveNew(Fn, ...arg) { if (typeof Fn !== 'function') { throw 'Fn must be a function' } // 1、创建一个新对象 let obj = new Object(); // 2、新对象可以访问到构造函数原型所在的原型链 obj.__proto__ = Object.create(Fn.prototype); // 3. 将构造函数的作用域赋给新对象,即this指针指向新对象 let result = Fn.call(obj, ...arg); // 4. 判断构造函数返回的是否是引用数据类型 let isType = (typeof result === 'object' && result !== null) || typeof result === 'function' return isType ? result : obj } // 上述1、2步骤可以替换为let obj = Object.create(Fn.prototype); 代码减少一行,且原型链嵌套层级减少一层。 // 上述2步骤也可以替换为obj.__proto__ = Fn.prototype; // 等等 function Animal(name) { this.name = name; } const cat = AcieveNew(Animal, 'niu') console.log(cat) // Animal {name: 'niu'} function AnimalFirst(name) { this.name = name; return { sex: 'boy' } } const dog = AcieveNew(AnimalFirst, 'niu') console.log(dog) // {sex: 'boy'}
-
apply、call、bind
-
使用
// apply、call、bind var name = 'global name' function input (sex) { console.log(this.name) console.log(sex) } let obj = { name: 'niu' } let anotherObj = { name: 'jiao' } input('boy'); // global name / boy input.call(obj, 'boy'); // niu / boy input.apply(obj, ['boy']); // niu / boy const bind = input.bind(obj, 'boy'); // niu / boy bind(); // niu / boy bind.call(anotherObj, 'girl') // niu / boy
- 三者都是用来改变this指针的指向;
- 三者之间传递参数不同,apply方法有点神经,传参是数组形式的,call和bind方法是正常的,正常传参即可。
- call、apply方法会立即执行,而bind方法则是会返回一个待执行函数,需要去手动调用执行。
- bind方法调用后返回的函数无法再重新通过这些方法改变this指针的指向。
- bind方法调用后参数会进行一个叠加过程。比如fn函数接受a和b两个参数,const newFn = fn.bind(obj, ‘a’); newFn(‘b’),这样就实现了参数的一个叠加过程。
-
实现
-
apply
// apply实现 Function.prototype.achieveApply = function (context, arg) { context = context || window; // 避免context对象属性重复 let proterty = Symbol(0); context[proterty] = this; const result = context[proterty](...arg); delete context[proterty]; return result; }
-
call
// call实现 Function.prototype.achieveCall = function (context, ...arg) { context = context || window; // 避免context对象属性重复 let proterty = Symbol(0); context[proterty] = this; const result = context[proterty](...arg); delete context[proterty]; return result; }
-
bind
// bind实现 Function.prototype.achieveBind = function (context, ...arg) { if (typeof this !== "function") { throw new Error("this must be a function"); } let self = this; let tempFn = function () { // bind方法后无法再次修改this指向,参数实现追加效果 self.achieveApply(this instanceof self ? this : context, [...arg, ...arguments]); } // 返回过程原型链对象上的属性不能丢失 if (this.prototype) { tempFn.prototype = Object.create(this.prototype) } return tempFn; }
-