day21

86 阅读9分钟

1.This指向问题

1this 全局中的this
    console.log(this);//window
    
2、函数中的this
   严格模式指向 undefined,非严格模式指向window  
   function fn(){
        console.log(this);
    }
   fn();
    
3、普通回调函数中的this
   严格模式指向 undefined,非严格模式指向window
   function fn(f) {
        f()
    }

   function fn1() {
        console.log(this)
    }
    
   fn(fn1);

4、arguments回调函数
   this指向回调当前函数的上下文环境中arguments对象
   function fn(){
        arguments[0]();
    }

   function fn1(){
        console.log(this)
    }

   fn(fn1);


5、部分方法中回调函数,setTimeOut,setInterval,Promise
   严格模式指向 undefined,非严格模式指向window
      setTimeout(function(){
        console.log(this)
      },2000)

    new Promise(function(resolve,reject){
        console.log(this);
        resolve();
    }).then(function(){
        console.log(this)
    })
    
6、部分包括thisArg的回调方法,forEach,map,filter,find,findIndex,flatMapsome、every、findLast,findLastIndex
   如果没有给入thisArg参数,this指向window或者undefined
       如果给入thisArg参数,this指向这个thisArg参数
    var arr=[1,2,3];
    arr.forEach(function(){
        console.log(this)    //this指向{a:1}
    },{a:1})        
    console.log(arr)
    
7、事件回调函数
   在事件回调函数或者on事件中,this指向被侦听的对象
   document.addEventListener("click",clickHandler);
    function clickHandler(e){
        console.log(this)
    }
    
    document.onclick=function(){
        console.log(this)    
    }
    
8、对象中的this
   对象中this指向
   属性中this指向对象外上下文环境中this指向
   对象属性:function 所创建的方法,函数中this指向当前对象(谁调用这个方法,this指向谁)
   对象属性(){} ES6方法,函数中this指向当前对象(谁调用这个方法,this指向谁)
   对象中属性使用箭头函数,设置方法,this指向对象外上下文环境中的this指向
    var obj={
        a:1,
        b:this.a,
        c:function(){
            console.log(this)  //谁调用c就是谁 //c里面的this 没人调用就指向obj
        },
        d(){
            console.log(this);
        },
        e:()=>{
            console.log(this)   
        }
    }
    obj.c();

    var o=Object.create(obj);
    o.c();
 
9、箭头函数
   箭头函数中this指向箭头函数外上下文环境中的this指向
    var fn=()=>{
        console.log(this)
    }

    document.addEventListener("click",fn);

10、call、apply、bind
    call和apply在使用时,非严格模式下,如果不是对象,数值、布尔值、字符都会被
        转换为对象类型并且让this指向这个对象类型,如果传入undefined或者nullthis
        将会指向window
        严格模式下,传入什么this指向什么
    bind

11、ES6面向对象
    构造函数中this指向实例化的对象
    实例化方法中的this,谁调用指向谁
    静态方法中this指向该类或者构造函数(禁止使用this)
    实例化属性上this指向当前实例化对象
    静态属性中this指向当前类或者构造函数(禁止使用thisvar b=3;
    class Box{
        b=1;
        a=this.b
        static b=5;
        static c=this.b;
        constructor(){
            // 构造函数中this指向实例化的对象
            console.log(this)
        }
        play(){
            console.log(this)
        }
        static run(){
            // 使用静态方法时禁止使用this
            console.log(this)
        }
    }

    class Ball extends Box{

    }
    var b=new Ball();
    b.play();
    console.log(Ball.c)
    

12、原型中this指向
    如果这个函数使用new实例化,这个是构造函数,所以this指向实例化的对象
    如果这个函数直接执行,this指向window或者undefined
    原型属性中使用thisthis指向当前实例化对象
    原型方法中this,指向当前实例化对象
    原型方法中使用箭头函数,this依照箭头函数的指向
    函数的方法中this执行当前函数,等同于ES6中静态方法,this禁止使用,指向这个类或者构造函数

    var b=1;
    function Box(){
        console.log(this);
    }
    Box.prototype.b=100;
    Box.prototype.a=this.b;
    Box.prototype.play=function(){
        console.log(this);
    }
    Box.prototype.run=()=>{
        console.log(this)
    }
    Box.play=function(){
        console.log(this)
    }

   var b= new Box();
   console.log(b)
   b.run();

2. 强行改变 this 指向

强行改变 this 指向
    + 不管你本身指向哪里, 我让你指向谁你就得指向谁
      => 统一语法: 函数.xxx()
      => 用来改变 this 指向的
    + 三个改变 this 指向的方法
      1. call()
      2. apply()
      3. bind()

  call
    + 语法:
      => 函数名.call()
      => 对象名.函数名.call()
    + 参数:
      => 第一个参数: 该函数本次调用时函数内的 this 指向
      => 第二个参数开始: 依次是该函数的每一个实参
    + 特点: 立即调用函数

  apply
    + 语法:
      => 函数名.apply()
      => 对象名.函数名.apply()
    + 参数:
      => 第一个参数: 该函数本次调用时函数内的 this 指向
      => 第二个参数: 是一个数组或者伪数组数据类型都行, 内部的每一个数据依次是该函数的实参
    + 特点: 立即调用函数
    + 特殊作用: 改变函数传递参数的方式

  bind
    + 语法:
      => 函数名.bind()
      => 对象名.函数名.bind()
    + 参数:
      => 第一个参数: 该函数本次调用时函数内的 this 指向
      => 第二个参数开始: 依次是该函数的每一个实参
    + 特点:
      => 不会立即调用函数, 而是返回一个新的函数      !!!!
      => 一个和原先一模一样的函数, 只是被锁定了 this
    + 特殊作用:
      => 修改一些不需要立即执行的函数的 this 指向
      => 例如: 事件处理函数, 定时器处理函数
 
 例:
 var obj = {
       a: function () {
    // setTimeout(function () {
    //     console.log(this);//window
    // }, 500);

    // call 和 apply都是立即执行函数,所以不适用于回调函数中
    // setTimeout(handler.call(obj),2000);
    // bind给函数内绑定一个obj并且返回一个新函数,2000毫秒后调用这个新函数
    setTimeout(handler.bind(obj),2000);
            function handler(){
                console.log(this)
            }
        }
    }
    obj.a();
    
bind给函数内绑定一个obj并且返回一个新函数,2000毫秒后调用这个新函数
 setTimeout(handler.bind(obj),2000);
    
 例:
        //thisArg 绑定函数中this的指向
        //...arg 执行函数传入的参数
    Function.prototype.bind1=function(thisArg,...arg){
        // console.log(this)//谁调用bind1,this就指向谁
        var f=this;
        // 执行bind1后,会返回一个新函数,当再次执行这个新函数或者回调这个函数时
        // 才会触发执行上面f
        return function(...arg1){
           return  f.apply(thisArg,[...arg,...arg1]);
        }
    }

3.setter和getter

1.set 属性名(value){           //set中有且仅有一个参数 而且是必填参数
存储用的内部属性名=value;
     。。。。当设置这个属性后随之需要的操作
}

Note:每个setter设置一个属性时,必须有一个参数value,
并且,我们需要用一个外部变量来接收这个参数,用于保存。因此setter写法基本固定

2.get 属性名(){
        。。。当获取这个属性时需要操作的内容
 return 内部存储的这个属性;  //get中不能使用参数 必须使用return返回一个值
}

Note:每个getter是获取一个属性,因此,必须有一个return返回内部存储的值

3.个人:
set中有且仅有一个参数 而且是必填参数
get中不能使用参数 必须使用return返回一个值
两者方法名完全相同                          (下面是a)

使用setget时必须要使用与之相对应的临时属性 (下面是_a)
原因:set本身是不能存储值的 所以要给临时变量来存储值

例:
 var obj={
        _a:1,
        set a(value){
            console.log(value)
            this._a=value;
        },
        get a(){
            return this._a;
        }
    }
 obj.a=10;
 console.log(obj.a); //10
 
/* 
    对象的属性,通常是用来存储值的
    对象的方法,通常用来处理多条语句,不能存储值

    set,get即可以存储值,也可以执行多条语句
    set,get调用方式和属性调用方式相同
    当赋值时,调用set方法
    当获取值,调用get方法
*/

例:
    var obj={
        _a:1,
        get a(){
            return this._a;
        }
    }

    /* 
        只读不能设置属性
        没有set就是只读
        没有get就是只写
    
    */
注意setter和getter设置的属性一般是成对出现,对应的相应属性。
如果仅出现get没有出现set,表示该属性只读,不可写值。  只读不能设置属性
如果仅出现set,没有使用get,表示该属性只写,不能获取,

删除getter或setter的唯一方法是:delete object[name]。delete可以删除一些常见的属性,getters和setters。

4.闭包

什么是闭包:
1.闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量

为什么要闭包:
1.出于种种原因,我们有时候需要得到函数内的局部变量值。但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现。

闭包的作用:
1. 在外部访问函数内部的变量
2. 让函数内的局部变量可以一直保存下去
3. 模块化私有属性和公共属性

闭包的原理:
全局变量生存周期是永久,局部变量生存周期随着函数的调用介绍而销毁。
闭包就是 在函数中定义且成为该函数内部返回的函数的自由变量 的变量,该变量不会随着外部函数调用结束而销毁。 
(注:不光是变量,函数内声明的函数也可以形成闭包)
当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包。

闭包的优点:
1.希望一个变量长期驻扎在内存中
2.避免全局变量的污染
3.私有成员的存在

闭包的缺点:  
1.占用内层空间 大量使用闭包会造成 栈溢出
2.会造成内存泄漏

如何优化闭包
由于闭包会一直占用内存空间,直到页面销毁,我们可以主动将已使用的闭包销毁:
将闭包函数赋值为null 可以销毁闭包

闭包的例子: // 里面的函数有权访问外部函数作用域中的局部变量
   function fn(){
        return function(){
            console.log("a")
        }
    }

    var f=fn();
    f();

5.柯理化和反柯理化

1.柯理化
函数柯理化
    + 闭包的一种高级应用
    + 意义: 把参数分开, 把不经常变换的参数锁死(以闭包的形式), 我们只需要在调用的时候传递会变化的参数即可
    
例:柯理化更方便的求和   (简单 简洁 别人看不懂 柯理化例子背下来)
    function currying(fn){
        var arr=[];
        return function(){
            if(arguments.length>0){      //apply的参数是可以以数组形式放进去 arguments是迭代器
                arr=arr.concat.apply(arr,arguments);  //迭代器都能放进去
            }else{
                return  fn.apply(null,arr);
            }
        }
    }
    //调用
    var getSum=currying(function(){
        return [].reduce.call(arguments,(v,t)=>v+t);//拿arguments 就是reduce里的this 
    })                                              //(v,t)=>v+t 把他的每一个元素拿去遍历 遍历后合并

    getSum(1,2,3);
    getSum(4,5);
    var s=getSum();
    console.log(s)//15
    
2.反柯理化
在JavaScript中,当我们调用对象的某个方法时,其实不用去关心该对象原本是否被设计为拥有这个方法,这是动态类型语言的特点。
可以通过反柯里化(uncurrying)函数实现,让一个对象去借用一个原本不属于他的方法。

例:
反柯里化函数
    function uncurrying(fn) {
       
     return function () {                                 //这个fn相当于执行了fn.call       
     return Function.prototype.call.apply(fn, arguments); //fn是传进来的函数 call里面本身是有this的(谁调用call,this就是谁) 使用fn替换call里的this
        }                                                 //打算用[].push这个函数来改变call里的this指向
    };

    var push=uncurrying([].push);  //把[].push这个函数传进去
    var splice=uncurrying([].splice);
    function fns(fn){
       var len= push(arguments,4,5,6);
        console.log(len,arguments) //6 Arguments(6) [1, 2, 3, 4, 5, 6, callee: ƒ, Symbol(Symbol.iterator): ƒ]
        splice(arguments,1,1,5);
        console.log(arguments); //Arguments(6) [1, 2, 3, 4, 5, 6, callee: ƒ, Symbol(Symbol.iterator): ƒ]
    }
    fns(1,2,3);