JavaScript基础(预解释,this指向)

198 阅读1分钟

JavaScript基础进阶版

JS数据类型

// JS中的数据类型:
// 基本数据类型
// number、string、boolean、null、undefined

// 引用数据类型
// object : {}、[]、/^$/、Date、
// function 

    var num = 12
    var obj = {name:'小红',age:22}
    function fn(){
        console.log('小红')
    }
    console.log(fn) //把整个函数的定义部分(函数本身)在控制台输出
    console.log(fn()) // 把当前函数执行的返回结果(return返回什么,返回值就是什么,如果没有return,默认就是undefined)

预解释

1、当浏览器加载html页面的时候,首先会提供一个供全局JS代码执行的环境 -> 全局作用域(global/window)

2、预解释(变量提升)

/**
 *  在当前的作用域中,JS代码执行之前,JS首先会默认的把所有带 var 和 function 的进行提前的声明或者定义
 *  2.1、理解声明和定义
 *  var num = 12;
 *  声明(declare): var num; -> 告诉浏览器在全局作用域中有一个num的变量了,如果一个变量知识声明了但是没有赋值,这个变量默认是undefined,
 *  注意:用let 定义的变量声明是不会提升的
 * 
 *  定义(defined): num = 12; -> 给变量进行赋值
 *  2.2、对于带var 和 function 关键字的在预解释的时候操作还是不一样的
 *     var  -> 在预解释的时候只是提前的声明
 *     function -> 在预解释的时候提前的声明 + 定义都完成了
 *  2.3、预解释只发生在当前的作用域下,例如:开始最对window作用域下的代码进行预解释,只有函数执行的
 *  时候才会对函数中的进行预解释
 */ 
    console.log(num)  // undefined 不会报错
    // console.log(num1) //报错 let 定义的变量声明不会提升
    console.log(fn(1,2,3,4))  // 可以在上面执行,因为预解释的时候声明 + 定义已经完成了
    var num  = 12
    let num1 = 12
    
    function fn(){
        return [...arguments]
    }

2.1、预解释五种情况

● 第一种
 // in: "num" in window 判断num 是否为window这个对象的一个属性,是的话返回true,不是的话返回false 
     
     var obj = {name:'小红',age:22}
     console.log('name' in obj) //true
     console.log('sex' in obj) // false

    //1、预解释的时候不管你的条件是否成立,都要把带var的进行提前的声明
    //winodw的预解释:var num; -> window.num
    if(!('num' in window)){ // ('num' in window) -> true    然后取反了 
         var num = 12
    }
    console.log(num)  // undefined
● 第二种
 //2、预解释的时候只预解释" = " 左边的,右边的是值得,不参与预解释
    //函数表达式: 把函数定义的部分当做一个值赋值给我们的变量/元素的某一个事件

    //window下的预解释: var fn;
    fn()  //  Uncaught TypeError: fn is not a function   
    var fn = function(){
        console.log('ok')
    }


    // fn()  -> ok
    // function fn(){
    //     console.log('ok')
    // }
    // fn()  -> ok

● 第三种
 // 3、自执行函数定义的那个function在全局作用域下不进行预解释,当代码执行到这个位置的时候定义和执行就会一起完成
    //自执行函数:定义和执行一起完成 iife
    (function(num){})(100)
● 第四种
// 4、函数体中 return 下面的代码虽然不执行了,但是需要进行预解释; return 后面跟着的都是我们返回的值,所以不进行预解释
    // 
    function fn(){
        // 预解释 : var num;
        console.log(num) // -> undefined
        return function (){

        }
        var num = 100
    }
    fn()
● 第五种
 //5、在预解释的时候,如果名字已经声明过了,不需要重新声明,但是需要重新赋值
    //在JS中如果变量的名字和函数的名字重复了,也算冲突
    //预解释: var fn; winodw.fn; fn = xxxfff000 window.fn = xxxfff000
    // var fn = 13
    // function fn(){
    //     console.log('ok')
    // }

    //window预解释:
    // 声明 + 定义 fn = xxxfff111
    // 声明 var fn ;(不需要重新声明)
    // 声明(不重复进行) + 定义 fn = xxxfff222
    // -> fn = xxxfff222
    fn() // -> 2
    function fn(){console.log(1)}
    fn() // -> 2
    var fn = 10 // -> fn = 10
    fn() // -> 10()   会报错 Uncaught TypeError: fn is not a function
    function fn(){console.log(2)}
    fn() // 因为上面报错了,所以下面不会执行

3、JS中内存分类

● 栈内存: 用来提供一个供JS代码执行的环境 -> 作用域(全局作用域/私有作用域)
● 堆内存: 用来存储引用数据类型的值 -> 对象存储的是属性名和属性值,函数存储的是代码字符串...

区分私有变量和全局变量

1、如何区分私有变量和全局变量

1.1在全局作用域作用下声明(预解释的时候)的变量是全局变量
1.2在私有作用域中声明的变量和函数的形参都是私有的变量
/**
 * 在私有作用域中,代码执行的时候遇到了一个变量,首先需要确定它是否为私有的变量,如果是私有的变量,那么
 * 和外面的没有任何关系;如果不是私有的,则往当前的作用域的上级作用域进行查找,如果上级作用域也没有则会
 * 继续往上查找,一直找到window为止...(作用域链)
 * 
 * 
 */

2、当函数执行的时候(直接目的:让函数体中的代码执行),首先会形成一个新的私有的作用域

2.1 如果有形参,先给形参赋值
2.2 进行私有作用域中的预解释
2.3 私有作用域中的代码从上到下执行
 // 函数形成一个新的私有的作用域保护了里面的私有变量不受外界的干扰(外面修改不了私有的,私有的也修改不了外面的) --> "闭包"

全局变量细节

● 在全局作用域中,带 var 和不带 var 的关系?

//区别: 带var的可以进行预解释,所以在赋值的前面执行不会报错;不带 var 的是不能进行预解释的,在前面执行会报错

       console.log(num)  //undefined 不报错
       var num = 12

       console.log(num2) // Uncaught ReferenceError: num2 is not defined 报错
       num2 = 12
 //关系:num2=12 -> 相当于给window增加了一个叫做num2的属性名,属性值是12
    //var num = 12 ->首先它相当于给全局作用域增加了一个全局变量num,但是不仅如此,它也相当于给window增加了一个属性名num,属性值是12

    var num = 12
    console.log(num) // 12

    num2 = 12
    console.log(num2)// 12  -> window.num2
 //私有作用域中出现的一个变量不是私有的,则往上级作用域进行查找,上级没有则继续向上查找,一直找到window为止,如果window下也没有呢?
    //第一种情况:如果是获取值 console.log(total)  -> 报错
    //第二种情况:如果是设置值 total = 100 ->相当于给window增加一个属性名total,属性值是100 不会报错
    function fn(){
        // console.log(total)  //Uncaught ReferenceError: total is not defined
        total = 100;
    }

    fn()
    console.log(total) //100  并不会报错,值设置上了


    //JS中如果在不进行任何特殊处理的情况下,上面的代码报错,下面的代码都不执行了

如何查找当前作用域的上一级作用域?

// 如何查找当前作用域的上一级作用域?
    // 看当前函数是在哪个作用域下定义的,那么它的上级作用域就是谁 -> 和函数在哪里执行的没有任何关系
    
    var num = 12
    function fn(){  // 因为fn是在window下定义的,所以fn的上级作用域是window
        var num = 120 
        return function(){  // 因为它是在fn函数里面定义的 所以它的上级作用域是 fn
            console.log(num)
        }
    }

    var f = fn()
    f() // 120  函数在哪里执行并不重要 ,要看它的作用域  它的上级作用域就是fn

    (function(){
        var num = 1200
        f() // 120 函数在哪里执行并不重要 ,要看它的作用域  它的上级作用域就是fn
    })()

内存释放和作用域销毁机制

堆内存

 // 对象数据类型或者函数数据类型在定义的时候首先都会先开辟一个堆内存,堆内存有一个引用地址,如果外面有变量等知道了这个地址,这个内存就被占用了,就不能销毁了
    let obj1 = {name:'张三'} // xxxfff000
    let obj2 = obj1
    //我们想让堆内存释放/销毁,只需要把所有引用它的变量值赋值为null就可以
    //如果当前的堆内存没有任何东西被占用了,那么浏览器会在空闲的时候把它销毁... 浏览器的垃圾回收机制
    obj1 = null
    obj2 = null

栈内存

//1、全局作用域
    //只有当页面关闭的时候全局作用域才会销毁


    //2、私有作用域(只有函数执行会产生私有的作用域)
    //一般情况下,函数执行会形成一个新的私有的作用域,当私有作用域中的代码执行完成后,我们当前作用域都会主动的进行释放和销毁
    //但是还是存在特殊的情况的:
    //当前私有作用域中的部分内存被作用域以外的东西占用了,那么当前的这个作用域就不能销毁了(闭包机制)
    //a:函数执行返回了一个引用数据类型的值,并且在函数的外面被一个其他的东西给接收了,这种情况下一般形成的私有作用域都不会销毁
    function fn(){
        let num = 100
        return function (){  //1.要返回一个引用类型的值
            num++
            console.log(num)
        }
    }
    let f = fn()  //2.函数外面要有变量接收它
    // fn执行形成的这个私有作用域就不能被销毁了
    f() //101   
    f() //102
    f() //103
    //b:在一个私有的作用域中给DOM元素的事件绑定方法,一般情况下我们的私有作用域都不销毁
    let odiv = document.getElementById('div')
    //通过DOM方法获取的元素/元素集合都是对象数据类型的值
     (function(){
         odiv.onClick = function(){

         }
     })()

     //c:下述情况属于不立即销毁->fn返回的函数没有被其他的东西占用,但是还需要执行一次呢,索引暂时不销毁,当返回的值执行完成后,浏览器会在空闲的时候把它销毁了 -> "不立即销毁"
     function fn(){
        let num = 100
        return function (){  //1.要返回一个引用类型的值
            num++
            console.log(num)
        }
     }
     fn()() //首先执行fn,返回一个小函数对应的内存地址,然后紧接着让返回的小函数再执行

this

JS中的this代表的是当前行为执行的主体; JS中的context(上下文)代表的是当前行为执行的环境(区域);

this是谁和函数在哪定义的和在哪执行的都没有任何的关系

如何区分this?

1、函数执行,首先看函数名前面是否有"." ,如果有话,"."前面是谁,this就是谁;没有的话this就是Window
//1演示

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

     var obj = {
         fn:fn
     }

     fn() //this ->window
     obj.fn() //this ->obj

     function sum(){
         fn() // this ->window
     }
     sum() // window

     var oo = {
         sum : function(){
             console.log(this) // this ->oo
             fn() //this -> window  前面没有"." 所以this就是window
         }
     }

     oo.sum(); // this ->oo
2、自执行函数(IIFE)中的this永远是window
  (function(){
         console.log(this) // this -> window
     })()
3、给元素的某一个事件绑定方法,当事件触发的时候,执行对应的方法,方法中的this是当前的元素
document.getElementById('div1').onclick = fn  //绑定这个事件 fn中的this就是 #div1
 document.getElementById('div1').onclick = function(){
     console.log(this) //注意 这里的this 才是 #div1 

     fn()// this -> window 前面没有"." 所以this就是window 
 }
4、在构造函数模式中,类中(函数体中)出现的this.xxx = xxx 中的this是当前类的一个实例
 function Person(name,age){
        //JS默认创建的对象就是我们的实例 xiaohong -> this 就是 xiaohong
        this.name = name  //->xiaohong.name = name 
        this.age = age
        this.hello = function(){
            console.log(`my name is ${this.name} `)
        }
       
    }

    let xiaohong = new Person('小红',22)
    xiaohong.hello()
 	let xiaoyan = new Person('小燕',22)
    xiaoyan.hello()

5、使用call/apply/bind来改变this的指向(一旦遇到call/apply/bind上述四条都没用了)
 let obj = {name : '小红'}
    function fn(num1,num2){
        console.log(num1 + num2)
        console.log(this)
    }
    // fn(100,200) // this -> window num1=100 num2=200
    // fn.call(100,200) //this -> 100 num1=200 num2=undefined
    // fn.call(obj,100,200)//this -> obj num1=100 num2=100
    // fn.call() // this -> winodw             在严格模式下 this -> undefined
    // fn.call(null) // this -> window         在严格模式下 this -> null
    // fn.call(undefined) // this -> window    在严格模式下 this -> undefined

小练习

 var num = 20 //60
    var obj = {
        num : 30,
        fn:(function(num){
            this.num *= 3
            num += 15
            var num = 45
            return function(){
                this.num *= 4
                num += 20
                console.log(num)
            }
        })(num) //->把全局变量num的值20赋值给了自执行函数的形参,而不是obj下的30,如果想是obj下的30,我们需要写obj.num
    }
    var fn = obj.fn
    fn()//65
    obj.fn() // 85
   console.log(window.num,obj.num) //240、120
========================================================================================>
    let div1 = document.getElementById('div1')
    let num = document.getElementById('num')

    div1.onclick = (function(){
        var count = 0
        return function(){
            count++
            num.innerHTML = count
        }
    })()

    //弊端:有一个不销毁的私有的作用域,所以占用内存


    //利用innerHTML的方式处理,每一次点击的时候都先到页面中获取最新的值,然后累加,最后把累加的结果重新放回去
    // div1.onclick = function(){
    //    num.innerHTML++
    // }
    //弊端:每一次都需要把页面中的内容先转换为字符串然后再累加,累加完再重新添加回去,当重新的添加的时候浏览器都要从新的渲染一下

    //利用自定义属性存储(推荐)
    div1.count = 0
    div1.onclick = function(){
        num.innerHTML = ++this.count
    }