首先是几条规律:

  • 简单调用函数时候,严格模式是undefined,非严格模式会绑定到 globalThis
  • new调用构造函数,构造函数内的this会被绑定到新创建的对象上
  • 通过 call/apply/bind 显示函数时候,this会绑定到指定参数的对象上
  • 通过上下文对象调用,this会绑定到该对象上
  • 箭头函数,this由外层作用域来决定

    new

new做了什么?

JS基础之继承、原型链、面向对象

this优先级

显式绑定 apply/call/bind/new,魔法互喷,哪个优先级更高?

  1. function foo(a) {
  2. console.log(this.a);
  3. }
  4. const obj1 = {
  5. a: 1,
  6. foo: foo,
  7. };
  8. const obj2 = {
  9. a: 2,
  10. foo: foo,
  11. };
  12. obj1.foo.call(obj2); // 2
  13. obj2.foo.call(obj1); // 1

答案是,显式绑定优先级更高。既然知道了答案,可以继续看

  1. function foo(a) {
  2. this.a = a;
  3. }
  4. const obj1 = {};
  5. var bar = foo.bind(obj1);
  6. bar(2);
  7. console.log(obj1.a); //2
  • bar 是foo的函数,但修改了this为obj1,通过隐式调用不会修改this指向
  • 调用foo函数,传参2,this是obj1,因此 obj1.a=2
  1. var baz = new bar(3)
  2. log(baz.a) // 3

bar本身已经改变了this指向,现在通过new来调用,返回了新实例 baz, this也指向baz ,此时 baz.a=3

结论是: new优先级比 bind高,可以覆盖掉

  1. function foo() {
  2. return (a) => {
  3. console.log(this.a);
  4. };
  5. }
  6. const obj1 = {
  7. a: 2,
  8. };
  9. const obj2 = {
  10. a: 3,
  11. };
  12. const bar = foo.call(obj1);
  13. console.log(bar.call(obj2)); // 2
  • bar 是foo函数,内部的this指向了obj1,因此this.a=2
  • 后续 把 bar里的this指向obj2 但是并不生效
  • 如果把foo里的箭头函数改掉,log会打印3

结论是: 箭头函数的绑定无法被修改✌️

实现bind

来实现一个bind吧。sad

一个基础版本

  1. Function.prototype.bind =
  2. Function.prototype.bind ||
  3. function (ctx) {
  4. var that = this;
  5. var args = [...arguments].slice(1);
  6. return function bound() {
  7. var innerArgs = [...arguments];
  8. var finalArgs = args.concat(innerArgs);
  9. return that.apply(ctx, finalArgs);
  10. };
  11. };

这里实现了bind,返回的函数,内部拼接了参数,使用了apply传递数组
但,我们已知 new的优先级会高于bind,因此,这里需要对来源,也就是调用方式进行判断
也就是在

  • bound函数中对 this instanceof 进行判断
  • 了解即可??:length

    call apply bind

手写一个

call

  1. Function.prototype.myCall = function(ctx) {
  2. if (typeof this !== "function") {
  3. throw new TypeError("error");
  4. }
  5. ctx = ctx || window;// 不传参数就是 window
  6. ctx.fn = this; // 创建fn
  7. const args = [...arguments].slice(1);// 多参数
  8. const result = ctx.fn(...args);
  9. delete ctx.fn;
  10. return result;
  11. };

apply

  1. Function.prototype.myApply = function(context) {
  2. if (typeof this !== 'function') {
  3. throw new TypeError('Error')
  4. }
  5. context = context || window
  6. context.fn = this
  7. let result
  8. // 处理参数和 call 有区别
  9. if (arguments[1]) {
  10. result = context.fn(...arguments[1])
  11. } else {
  12. result = context.fn()
  13. }
  14. delete context.fn
  15. return result
  16. }

bind

  1. Function.prototype.myBind = function (context) {
  2. if (typeof this !== 'function') {
  3. throw new TypeError('Error')
  4. }
  5. const _this = this
  6. const args = [...arguments].slice(1)
  7. // 返回一个函数
  8. return function F() {
  9. // 因为返回了一个函数,我们可以 new F(),所以需要判断
  10. if (this instanceof F) {
  11. return new _this(...args, ...arguments)
  12. }
  13. return _this.apply(context, args.concat(...arguments))
  14. }
  15. }

new

自己写一个new?

  • 新生成对象
  • 链接到原型
  • 绑定this
  • 返回新对象
  1. functon create(){
  2. let obj={} // 1 新对象
  3. let Con=[].shift.call(arguments) // 获取传入参数,构造函数
  4. obj.__proto__=Con.prototype // 绑定空对象原型
  5. let result = Con.apply(obj,arguments) //绑定this并执行构造函数
  6. return result instanceof Object?result:obj
  7. }

instanceof

自己实现一个 instanceof

  1. function myInstanceof(left,right){
  2. let prototype=right.prototype
  3. left = left.__proto__
  4. while(true){
  5. if(left===null || left===undefined)
  6. return false
  7. if(prototype===left) return true
  8. left=left.__proto__
  9. }
  10. }

获取右边的原型。获取左边的原型。循环判断原型,直到null

  1. Function.prototype.myCall = function(ctx) {
  2. if (typeof this !== "function") {
  3. throw new TypeError("error");
  4. }
  5. ctx = ctx || window;// 不传参数就是 window
  6. ctx.fn = this; // 报存this
  7. const args = [...arguments].slice(1);// 多参数
  8. const result = ctx.fn(...args);
  9. delete ctx.fn;
  10. return result;
  11. };

-1 参考链接

  • 《前端开发核心知识进阶》第一部分