this指向 闭包和作用域

160 阅读5分钟

this 上下文context

this是在执行时上下文动态决定的。不是创建的时候后定义的。

  1. 函数直接调用的this执行的是window(函数表达式,匿名函数,函数嵌套)
  2. this隐式绑定(this指向调用堆栈的上一级,也就是谁调用this指向的是谁)
const test={
name:"测试",
fn(){
consoole.log(this)
}
}
obj.fn=fn
obj.fn() //因为是obj 调用的fn 所以fn中this的指向是obj
const test={
name:"测试",
fn(){
consoole.log(this)
}
}
let f1=obj.fn //直接将fn的函数体赋值给f1
f1()//this答应出来是window   

2.1 改变this(隐式改变)

 const foo = {
        bar: 10,
        fn: function() {
            console.log(this.bar);
            console.log(this);
        }
    }
    // 取出
    let fn1 = foo.fn;
    // 执行
    fn1();
    /// this的改变
    const o1 = {
        text: 'o1',
        fn: function() {
            // 直接使用上下文 
            return this.text;
        }
    }

    const o2 = {
        text: 'o2',
        fn: function() {
            //调用
            return o1.fn();
        }
    }

    const o3 = {
        text: 'o3',
        fn: function() {
            // 直接内部构造
            let fn = o1.fn;
            return fn();
        }
    }

    console.log('o1fn', o1.fn());///o1
    console.log('o2fn', o2.fn());///o1
    console.log('o3fn', o3.fn());///undefine 此时的this指向的是window
    
    ///改变o2的this指向
    
      const o2 = {
        text: 'o2',
        fn:o1.fn //改变o2中的fn指向 让其指向o1的函数内存,此时如果通过o2.fn()调用的话  则显示的是o2
    }

3.显示绑定(apply,bind,call)

 function foo() {
        console.log('函数内部this', this);
    }
    foo();

    // 使用
    foo.call({a: 1}); //第一个参数是this改变的对象,后面则是调用函数的参数
    foo.apply({a: 1});//第一个参数是this改变的对象,第二个参数是一个数组表示的是调用函数的参数

    const bindFoo = foo.bind({a: 1}); ///返回的是改变this后调用函数的执行结果
    bindFoo();//需要调用执行

3.1 apply bind call 的区别 这三者都是手动去改变this的指向的 不同点在于call和apply的第一个参数都是this的指向,call的其他参数是执行函数的参数,apply的第二个参数是个数组 代表的是执行函数的参数,bind是直接返回一个this改变后的执行函数结果,需要调用执行。

3.2 手写bind apply call

  //手写原理的思路:
  说明原理写下注释,根据注释写下代码
 * 手写bind*
  // bind 返回一个函数  返回原函数的执行结果,传参不变
  Function.prototype.mybind=function(){
   const _context=this
   //通过call改变数组slice的方法获取到传入进来的参数 转成数组
  // const args=Array.prototype.slice.call(arguments)
  //参数转数组
   const args=Array.form(arguments)
   //参数转输入
  // const args=[...new Set(arguments)]
  //第一个参数是新的this
  let  newargs=args.shift()
  //bind返回的是一个函数
   return function(){
  //返回的函数内部执行调用函数并且改变了this
        return  _context.apply(newargs,args)
 }
 }
* 手写apply*
Function.prototype.myapply=function(context){
if(typeof this !='Function'){
 throw new Error('no function')

}
//取参数的第一个 进行边缘判断
context =context||window
//将this指向对象的函数
context.fn=this
//执行改变后this的函数
let result= arguments[1]?context.fn(...arguments[1]):context.fn()
//删除定义的函数fn
delete context.fn
  //返回执行结果
 return result
 }
 手写call
 
 Function.prototype.mycall=function(context){
//定义一个内部的对象  接受this的改变

 context =context||window
 //将this指向该对象的函数
 context.fn=this
 //执行改变后this的函数
 let result= arguments[1]?context.fn(... [...new                 Set(arguments)].splice(1)):context.fn()
 //删除定义的函数fn
delete context.fn
//返回执行结果
return result
}

4. 类中的this 类中的构造函数或者方法中的this指向的是new 实例后的对象

闭包

闭包一个函数可以访问到另一函数内部的变量这种组合就形成了闭包,常见的模式有 函数嵌套函数作为返回值函数作为参数,匿名函数闭包(立即执行函数-函数作为参数)

优缺点:

优点:闭包可以延长变量的生命周期,可以让内部的变量在外部使用。

缺点:容易造成内存泄漏

内存泄漏指的是变量一直占用内存空间 造成其他变量不能使用该空间。内存溢出则指的是占用的内存超出了分配的内存或者剩余内存。

函数作为返回值
function test(){
let aa='myparentdata'
return function(){
console.log(aa)
}
}

test()()//myparentdata
//test函数内部定义了aa 可以在test函数外部获取到
函数作为参数
let content=''
function test1(fn){
content='外部函数'
fn()
}
function test2(){
console.log('内部函数参数传递')
}
test1(test2)

函数嵌套
    let counter = 0;

    function outerFn() {
        function innerFn() {
            counter++;
            console.log(counter);
            // ...
        }
        return innerFn;
    }
    outerFn()();
事件处理(立即执行函数)
//其根本也是函数作为参数
 let lis = document.getElementsByTagName('li');

    for(var i = 0; i < lis.length; i++) {
        (function(i) {
            lis[i].onclick = function() {
                console.log(i);
            }
        })(i);
    }
实现私有变量(通过闭包实现)

底层的api或者对外暴露的函数的时候我们一般在我们的封装方法里面定义私有变量,使用者不可以通过自己的方式对于这些变量进行修改,只能通过我们对外暴露的方法去修改 这个用到了闭包,通过函数内部的定义,在返回的函数中可以进行数据处理。(外部可以访问内部的变量等)


function createStack() {
        const items = []; //私有的变量
        return {
            push(item) { //通过对外暴露的方法去修改
                items.push(item);
            }
        }
    }


闭包的常见场景
  1. setTimeout 原生的setTimeout 的回调函数不能带有参数
function fn(a){
return  function(){
console.log(a)
}
}
let  test= fn(1)
setTimeou(test,1000)

  1. 防抖节流(搜索,窗口扩展) 防抖:高频率触发的事件,在指定的单位时间内,只响应最后一次,如果在指定的时间在触发,则重新计算时间(后面触发的事件执行,替代了前面的事件)
function debounce(fn,delay){
let timer =null
return function(){
if(timer){
  clearTimeout(timer)
 }
 timer=setTimeout(fn,delay)
}
}

节流:高频率触发的事件,在指定的单位时间内,只响应第一次(前面触发的执行前,忽略后面的事件)(鼠标滚动,点击)

function throttle(fn,delay){
let timer =true
retrun function(){
  if(!timer){ //如果换在执行  则直接return掉
    return false
  }
  timer=false   //改变timer的状态
  setTimeout(()=>{
  fn()
  timer=true //当执行下一个fn的时候修改状态
  },delay)

}

}

作用域

作用域就是变量或者对象可以在某些特定的层级或者环境被访问。该层级或者区域就被称为作用域。函数内部的使用的变量总取的是离他最近的变量值

 let a ='12'
function geta(){
let a=23
console.log(a) ///23
}
console.log(a) //'12'

作用域可分为全局作用域和局部作用域 1.全局作用域(在执行代码中可以在任何函数中或者执行代码中的任何部位都可访问的变量 可以全局变量,所在的作用域称为全局作用域)

let a ='12'
function geta(){
console.log(a)
}
console.log(a)
//a在函数内外部都可以访问,a变量是全局的变量,所处的作用域也就是全局作用域

2.局部作用域(变量在函数或者{}内部定义 称为局部变量,局部变量所处的作用域叫做局部作用域,局部作用域的变量不能被外部使用,只能在内部使用,或者内部嵌套函数使用)

console.log(aa) //报错 访问不扫aa  出现aa没有定义
function testaa(){
let aa = 12;
console.log(aa)
}

作用域连

作为的作用域链就是 程序访问一个属性或者变量的话,在自身的作用域中如果没有找到的话,则会向上查找,在父级的作用域中再次查找,直到找到对应的属性或者变量为止。

function test1(){
let aa='第一级作用域'
console.log(aa)
test2()
function test2 (){
let bb='第二层作用域'
console.log(bb)
console.log(aa) //第一级作用域
}
}

通过暂时性死区的方式去取消全局作用域,使用块级作用域

if(true){
let aa='死区'
}
console.log(aa)//报错