(1)关于this指向问题

关于this指向问题

一.new的实现原理是什么?

在js中,只要new一个函数,就会new一个对象。

   对于new创建的对象
   var arr =new Array()

new的实现原理(new的过程 4步骤)

  1. 在内存中创建一个空对象;
  2. 将构造函数的作用域给新对象(this指向这个新对象)
  3. 执行构造函数中的代码(为这个新对象添加看属性)
  4. 如果构造函数中没有返回其他对象,则返回this,即创建的这个新对象;否则,返回构造函数中返回的对象。

手写实现new操作符

       // new的实现原理
        function _new(Con,...args){   // Con: 接收的是一个构造函数,args:是传入构造函数的参数
            let obj={};  //创建空对象
            obj._proto_=Con.prototype;  //设置空对象的原型 obj是Con的实例
            let result=Con.apply(obj,args); //绑定this,将属性和方法添加到空对象上
            return result instanceof Object ? result :obj;  // 如果这个函数没有返回其他对象,则返回创建的新对象obj;
        }

        // 构造函数Con
        function Test(name,age){
            this.name=name;
            this.age=age;
        }
        Test.prototype.sayName=function(){
            console.log(this.name);
        }

        // 实现一个new操作符
        const a=_new(Test,'ZYW','20')
        console.log(a.age);

二、this的指向

this的指向是在函数被调用的时候确定的。也就是在执行上下文被创建时确定的。
因此,在同一个函数中,由于调用方式的不同,this指向不一样的对象。
除此之外,在函数执行过程中,this一旦被确定,就不可能再更改了。

1.this的4种指向

(1)函数调用模式:直接调用的函数,this被绑定为全局对象。在浏览器环境下是window对象,在node环境下是空对象{}

(2)方法调用模式:对象调用,this指向该对象。(就是当函数被存为一个对象属性时,被称为这个对象的方法)

(3)构造函数调用模式:通过new的方式进行绑定,this指向这个新创建的对象。a.当new返回的是一个对象时,this就会指向返回的这个新对象, b. 当new返回的不是一个对象时,this还是指向函数的实例。

(4)箭头函数中的this:箭头函数没有自己的this,在调用箭头函数的时候,不会隐式的调用this参数,而是从定义的函数定义上下文。

补充:可以通过调用函数的apply call bind来改变this的指向。

2.结论

在一个函数上下文中,this由调用者提供,由调用方式决定。如果要准确确定this的指向,找到函数的调用者以及区分他是否独立调用十分关键。

  1. 如果函数独立调用,那么该函数内部的this,指向undefined。但是在非严格模式下,当this指向undefined时,它会被自动指向全局对象。
// 为了能够准确判断,我们在函数内部使用严格模式,
//因为非严格模式会自动指向全局
function fn() {
  'use strict';
  console.log(this);
}

fn();  // undefined     因为fn是调用者,独立调用
window.fn();  //Window    fn是调用者,被window所拥有

  1. 如果调用者函数,被某一个对象所拥有,该函数在调用时,内部的this指向该对象;
var a = 20;
function fn() {
  function foo() {
    console.log(this.a);    
  }
  foo();
}
fn();         //20

  1. 箭头函数的this情况
let obj = {
    age: 20,
    info: function() {
        return () => {
            console.log(this.age); //this继承的是外层上下文绑定的this
        }
    }
}

let person = {age: 28};
let info = obj.info();
info(); //20

let info2 = obj.info.call(person);
info2(); //28
  1. 练习
//1)
var a = 20;
var obj = {
  a: 10,
  c: this.a + 20,   //由于并没有作用域限制,这里的this.a它依然处在全局作用域中,this指向为window对象。
  fn: function () {
    return this.a;
  }
}

console.log(obj.c);      //40
console.log(obj.fn());   //10



//2)
var a = 20;
var foo = {
  a: 10,
  getA: function () {
    return this.a;
  }
}
console.log(foo.getA()); // 10

var test = foo.getA;
console.log(test());  // 20


//3)
var a = 20;
function getA() {
  return this.a;
}
var foo = {
  a: 10,
  getA: getA
}
console.log(foo.getA());  // 10

//4)
function foo() {
  console.log(this.a)
}

function active(fn) {
  fn(); // 真实调用者,为独立调用
}

var a = 20;
var obj = {
  a: 10,
  getA: foo
}

active(obj.getA);   //20

3.使用call,apply显示指定this 【归结为显示绑定】

JavaScript内部提供了一种机制,让我们可以自行手动设置this的指向。它们就是call与apply。所有的函数都具有这两个方法。它们除了参数略有不同之外,其功能完全一样。

  1. 第一个参数都为this将要指向的对象。、
  2. 后面的参数 都是向将要执行的函数传递参数。
    区别:call 是以一个一个的形式传递,apply是以数组的形式传递。
//1.改变this指向
function fn() {
  console.log(this.a);
}
var obj = {
  a: 20
}

fn.call(obj);  //20   将fn的内部this绑定为obj

//2.call与apply不同的传递参数的形式
function fn(num1, num2) {
  console.log(this.a + num1 + num2);
}
var obj = {
  a: 20
}

fn.call(obj, 100, 10); // 130
fn.apply(obj, [20, 10]); // 50

三、 如何改变this的指向?

通过调用函数的call,apply,bind 三种方法来改变this的指向。
三者的不同点:

传参方面:

  • call的传递是单个的传递(数组也可以);
  • apply的传递参数是数组形式,传递单个会报错;
  • bind没有规定,传递值和数组都可以。

执行方面:

  • call和apply函数的执行是直接执行的;
  • bind函数会返回一个函数,然后当调用这个函数的时候,才会执行。

注意:当call,apply或者bind传入的第一个参数是undefined / null时,严格模式下会抛出错误。非严格模式下,应用的是默认绑定规则(即:this指向全局对象(浏览器环境为window,node环境为global))

function info(){
    //node环境中:非严格模式 global,严格模式为null
    //浏览器环境中:非严格模式 window,严格模式为null
    console.log(this);
    console.log(this.age);
}
var person = {
    age: 20,
    info
}
var age = 28;
var info = person.info;
//严格模式抛出错误;
//非严格模式,node下输出undefined(因为全局的age不会挂在 global 上)
//非严格模式。浏览器环境下输出 28(因为全局的age会挂在 window 上)
info.call(null);

手写call、apply、bind

手写 call

// 手写call
        // this为调用的函数
        // context是参数对象
        Function.prototype.myCall=function(context){
                if(typeof this!=='function'){                //判断调用者是否为函数
                    throw new TypeError('Error');
                }    
            context=context||window                          // 不传默认参数为window
            context.fn=this                                  // 新增fn属性,将值设置为需要调用的函数
            const args=Array.from(arguments).slice(1);  // 将arguments转化为数组,将call的参数提取出来 [...]
            const result=context.fn(...args);           //传递调用函数
            delete context.fn;                         // 删除函数
            return result;                             // 返回执行结果
        }

        // 普通函数
        function print(age){
            console.log(this.name+''+age);
        }

        // 自定义对象
        var obj={
            name:'zyw',
        }
        //调用函数的call方法
        print.myCall(obj,1,2,3);

手写 apply

// 手写apply
     Function.prototype.myApply=function(context){   // context为可选参数,如果不传的话,默认为上下文window
         if(typeof this !=='function'){  //判断调用者是否为函数
             throw new TypeError('Error')
         }
         context=context||window    //不传参数默认为window
         context.fn=this            // 新增fn属性,将值设置为需要调用的函数
         let result;                //返回执行结果
         if(arguments[1]){          // 判断是否有参数传入
            result=context.fn(...arguments[1])
         }else{
             result=context.fn()
         }
         delete context.fn;         //删除函数
         return result;             // 返回执行结果
     }
     
     // 普通函数
     function print(age,age2,age3){
            console.log(this.name+''+age+''+age2+''+age3);
        }

        // 自定义对象
        var obj={
            name:'zyw',
        }
        //调用函数的call方法
        print.myCall(obj,[1,2,3])

手写 bind

// 手写bind
     Function.prototype.myBind=function(context){
         if(typeof this!=='function'){    //判断调用者是否为函数
              throw new TypeError('Error')
     }
     const args=Array.from(arguments).slice(1);   //截取获取的参数
     const _this=this;                           //调用函数的指向
     return function F(){                        //返回一个函数
       //因为返回了一个函数,我们可以用new F(),所以需要判断
       // 对于new的情况来说,不会被任何方式改变this
       if(this instanceof F){
            return new _this(...arg,...arguments)                
        }else{
           return _this.apply(context,args.concat(...arguments))
         }
      }
    }

     // 普通函数
     function print( ){
            console.log(this.name);
        }

        // 自定义对象
        var obj={
            name:'zyw',
        }
        //调用函数的bind方法
        let F=print.myBind(obj,[1,2,3])
        let obj1=new F();
        console.log(obj1);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值