金色小芝麻

vuePress-theme-reco 金色小芝麻    2021 - 2023
金色小芝麻 金色小芝麻

Choose mode

  • dark
  • auto
  • light
主页
分类
  • JavaScript
  • BUG复盘
  • SVG笔记
  • TypeScript
  • 个人总结
  • CSS笔记
  • 开发工具
  • 前端入门
  • Vue2.0
  • 性能优化
  • 架构学习
  • 每日一题
标签
时间轴
社交
  • 掘金 (opens new window)
author-avatar

金色小芝麻

83

文章

27

标签

主页
分类
  • JavaScript
  • BUG复盘
  • SVG笔记
  • TypeScript
  • 个人总结
  • CSS笔记
  • 开发工具
  • 前端入门
  • Vue2.0
  • 性能优化
  • 架构学习
  • 每日一题
标签
时间轴
社交
  • 掘金 (opens new window)
  • 前端入门

    • 博客搭建过程及相关配置
    • 数组中的16种常用方法
    • JS中的数据类型简析——基本数据类型值
    • JS中的数据类型object基础
    • JS中三种常见的判断
    • 数据类型之间的区别(堆内存Heap VS 栈内存Stack)
    • JS基础练习题及解析
    • 开关灯的小案例
    • JS中数据类型检测方法——typeof
    • 条件判断几个小练习
    • JS循环——for循环
    • 自定义属性实现选项卡小案例
    • 字符串中的12种常用方法
    • DOM操作的简单原理
    • JS实现隔行变色鼠标跟随小案例
    • JS中function的基础知识
    • JS中数组去重的三种方法
    • JS中时间格式化的三种方法
    • JS中URL参数处理的三种方法
    • JS小案例——获取随机验证码
    • DOM操作之——获取DOM标签的9种方式
    • DOM操作之——DOM节点类型及属性
    • DOM操作之——元素的增删改、样式修改、插入内容
    • JS中THIS相关问题梳理
    • JS中的变量提升机制
    • 在全局:私有上下文中:带VAR和不带VAR的区别
    • 作用域和作用域链查找机制
    • JS中堆栈内存的释放问题
    • JS中的闭包机制
    • ES3和ES6定义变量的区别
    • JS中的单例和工厂设计模式
    • JS中的面向对象OOP理论基础
    • 构造函数创建自定义类
    • JS中的原型和原型链
    • Math中常用的9种方法
    • 从一道阿里经典面试题剖析函数三种角色
    • 重写内置call
    • 重写一个内置new
    • 浏览器渲染页面的主体流程
    • 减少DOM的回流和重绘
    • JS中的多种继承方式
    • JS中数据类型检测四种方式的优缺点
    • JS中的正则表达式&&全面梳理
    • 非严格模式 🆚 严格模式的区别
    • 数组方法reduce、filter、flat
    • 获取数组中最大值/最小值的三种基础方法
    • 轮播图——渐隐渐显版
    • 深克隆 VS 浅克隆|深比较 VS 浅比较|回调函数
    • 轮播图——左右切换版
    • 事件及事件绑定、事件对象及事件传播
    • 从在地址栏输入网址到看到页面的过程&&AJAX基础
    • 想自学JS吗?想提升JS底层原理吗?76张脑图带你彻底搞懂原生JS
    • 面试手写API系列
    • 30张脑图带你从零开始学VUE
    • JS中的盒子模型
    • 初识JS-基础中的基础
    • 前端发展简史
    • JS中的三大类输出方式

JS中`THIS`相关问题梳理

vuePress-theme-reco 金色小芝麻    2021 - 2023

JS中THIS相关问题梳理

金色小芝麻 2020-02-23

this就是js里的关键字,有特殊意义,代表函数执行主体。

# 一、定义

  • 函数执行的主体(不是上下文):意思是谁把函数执行的,那么执行主体就是谁

# 二、使用情况

  • 1、全局作用域里的this是window,全局作用域下相当于是window.fn()执行只是把window.省略了(严格模式下是undefined)。

    console.log(this === window) // true
    
    window.a  = 13;
    console.log(this.a) // 13
    
  • 2、函数里的this,看执行主体前有没有点,如果有点,那点前面是谁,函数里的this就是谁,如果没有点,那函数里的this就是window,严格模式下是undefined。

    function fn(){
        console.log(this)
    }
    fn();//window
    
    let obj = {
        fn:function(){
            console.log(this) 
        }
    }
    obj.fn();//obj
    var f = obj.fn;
    f();//window
    
  • 3、自执行函数里的this是window或undefined(严格模式下)

    (function(){
        console.log(this);//==>window
    })();
    ~function(){}();//==>window
    +function(){}();//==>window
    -function(){}();//==>window
    !function(){}();//==>window
    
  • 4、回调函数里的this一般情况下是window

    let ary = [1,2,3];
    ary.forEach(function(item,index){
        console.log(this)
    })
    //================================//
    setTimeout(function(num){
        console.log(num)
        console.log(this)
    },1000,1)
    //================================//
    function fn(m){
        m()
    }
    fn(function(){
        console.log(this)
    })
    
  • 5、箭头函数没有this:

    但是要是在箭头函数里使用this,他就会往上一级作用域查找,如果上一级作用域也没有,那就继续往上找,直到找到全局的window为止

    let obj = {
        num: 2,
        fn: function () {
            // this-->obj
            let m = () => {
                // this-->obj
                console.log(this.num) // obj(向上一级作用域查找)
            }
            m()
        }
    }
    obj.fn()
    
  • 6、构造函数里的this是当前实例

  • 7、实例原型上的公有方法里的this一般是当前实例(下面改变this的方法中有体现)

  • 8、给元素绑定事件行为,那事件里的this就是当前被绑定的元素本身

    btn.onclick = function(){
        console.log(this) // btn
    }
    

# 三、面向对象中有关私有/公有方法中的THIS问题

总结下来this在面向对象中,主要还是看是谁执行的,也就是执行函数点前面是谁

  • 1、方法执行,看前面是否有点,点前面是谁THIS就是谁
  • 2、把方法总的THIS进行替换
  • 3、再基于原型链查找的方法确定结果即可

# 四、改变this指向:call/apply/bind

每一个函数(普通函数/构造函数/内置类)都是Function这个内置类的实例,所以:函数.__proto__===Function.prototype,函数可以直接调取Function原型上的方法

call / apply / bind

  • 原型上提供的三个公有属性方法
  • 每一个函数都可以调用这个方法执行
  • 这些方法都是用来改变函数中的THIS指向的

# 1、call

# 语法:

  • 函数.call(context,params1,params2....)

# 定义:

  • 函数基于原型链找到Function.prototype.call这个方法,并且把它执行,在call方法执行的时候改变里面的this关键字

# 作用:

  • 让当前函数执行
  • 把函数中的THIS指向改为第一个传递给CALL的实参
  • 把传递给CALL其余的实参,当做参数信息传递给当前函数

# 注意:

  • 如果执行CALL一个实参都没有传递,非严格模式下是让函数中的THIS指向WINDOW,严格模式下指向的是UNDEFINED
  • fn.call(null);
    • //=>this:window
    • 严格下是null(第一个参数传递的是null/undefined/不传,非严格模式下this指向window,严格模式下传递的是谁this就是谁,不传this是undefined)

# 使用方法:

function fn(){}
fn.call(); //=>fn函数基于原型链找到Function.prototype上的call方法,并且让其执行(执行的是call方法:方法中的this是fn)
fn.call.call(); //=>fn.call就是Function.prototype上的call方法,也是一个函数,只要是函数就能用原型上的方法,所以可以继续调用call来执行


Function.prototype.call = function $1(){
//...
}
fn.call => $1
fn.call() => $1()  this:fn
fn.call.call() => $1.call() => 继续让call执行,this:$1

实例.方法():都是找到原型上的内置方法,让内置方法先执行(只不过执行的时候做了一些事情会对实例产生改变,而这也是这些内置方法的作用),内置方法中的THIS一般都是当前操作的实例

# call简单实现原理

```javascript
//=>我们的需求是想让FN执行的时候,方法中的THIS指向OBJ
obj.fn(); //=>Uncaught TypeError: obj.fn is not a function  
//因为此时obj中并没有fn这个属性

-------解决办法---------

obj.fn = fn;
obj.fn(); //=>this:obj  //=>'OBJ'
delete obj.fn;//=>对象中原本没有,所以使用后要删掉
```

# 自己基于原生JS简单的实现内置的call方法

  • 详细梳理:

        ~ function () {
            /*
            * myCall:改变函数中的THIS指向 
            *   @params
            *      context 可以不传递,传递必须是引用类型值(因为后面要给它加$fn的属性)  
            */
            function myCall(context) {
    	        //this:sum 也就是当前要操作的这个函数实例
    	        context = context || window;
    	        let args = [], //=>除第一个参数外剩余传递的信息值
    		        result;
    	        for (let i = 1; i < arguments.length; i++) {
    		        args.push(arguments[i]);
    	        }
    	        context.$fn = this;
    	        result = context.$fn(...args); //=>args=[10,20] ...是ES6中的展开运算符,把数组中的每一项分别的展开传递给函数 //=>context.$fn(10,20)
    	        delete context.$fn;
    	        return result;
            }
            /* 扩展到内置类的原型上 */
            Function.prototype.myCall = myCall;
        }();
    
  • 可简写为

        function myCall(context,...arg){
            //context-->obj    this-->fn
            context = context || window; // 如果你不传参、或者传null、传undefined,那context的值都是window
            let res = null; // 创建一个变量,准备接收函数执行结果
            context.fn = this; // 把fn增加到obj里
            res =  context.fn(...arg); // 让fn指向
            delete context.fn; // 把fn从obj里删除
            return res; // 把this的返回值return出去
         }
        Function.prototype.myCall = myCall;
        console.log(fn.myCall(obj,1,2)) // 'ss'
    
  • 以题为例分析

最终我们可以得出:

  • 一个CALL是让左边函数执行(this是传递的参数)
  • 多个CALL是让最后传参的函数执行(this是window/undefined)

# 2、apply

和call方法一样,都是把函数执行,并且改变里面的this关键字的,唯一的区别就是传递给函数参数的方式不同

  • call是一个个传参
  • apply是按照数组传参
let obj={name:'OBJ'};
let fn=function(n,m){
    console.log(this.name);
}
//=>让fn方法执行,让方法中的this变为obj,并且传递10/20
fn.call(obj,10,20);
fn.apply(obj,[10,20]);

# 3、bind

和call/apply一样,也是用来改变函数中的this关键字的,只不过基于bind改变this,当前方法并没有被执行,类似于预先改变this

# 语法:

  • 函数.bind(context,params1,....)

# 区别:

  • bind是预处理this,他并不会让函数执行
  • bind方法的返回值是一个改变this之后的新函数

# 作用:

  • 把函数中的THIS指向通过预处理的方式改为第一个传递给BIND的实参
  • 一般使用在绑定点击事件,不让函数立即执行时
    let obj={name:'OBJ'};
    function fn(){
        console.log(this.name);
    }
    document.body.onclick=fn; //=>当事件触发,fn中的this:BODY
    
    //=>点击BODY,让FN中的THIS指向OBJ
    //document.body.onclick=fn.call(obj); //=>基于call/apply这样处理,不是把fn绑定给事件,而是把fn执行后的结果绑定给事件
    document.body.onclick=function(){
        //this:BODY
        fn.call(obj);
    }
    document.body.onclick=fn.bind(obj); 
    

# 注意:

  • 在IE6~8中不支持bind方法
  • 预先做啥事情的思想被称为“柯理化函数”

# 优点:

  • bind的好处是:通过bind方法只是预先把fn中的this修改为obj,此时fn并没有执行呢,当点击事件触发才会执行fn(call/apply都是改变this的同时立即把方法执行)

欢迎来到 金色小芝麻
看板娘