JS三座大山之作用域和闭包(1)

执行上下文

在讲作用域之前我们来说一个知识点:执行上下文。每一个函数都有自己的执行上下文EC(执行环境 execution context),并且每个执行上下文中都有它自己的变量对象VO(Variable object),用于存储执行上下文中的变量 、函数声明 、函数参数,这解释了js如何找到我们定义的函数和变量。并且函数是js中唯一一个能创建出作用域的,注意:for,if()else()不能创建作用域。看一段代码

console.log (a)       // undefined
var a = 10

此时打开控制台就会打印undefined,这是因为在代码还没执行之前,a会被提前声明,此时的代码可以理解为

var a 
console.log (a)      // undefined
var a = 10

代码执行到console.log (a) 时a只声明了,并没有赋值,所以结果当然是undefined。再来看一段代码

fn ("zhangsan",20)
function fn (name) {
    age = 20
    console.log (name,age)      // "zhangsan" 20
    var age
}

此时打印的结果就是“zhangsan”,20。我们可以将代码理解为

function fn (name) {
    var age
    age = 20
    console.log (name,age)        // "zhangsan" 20
}
fn ("zhangsan",20)

一段或者一个函数都会生成执行上下文,针对一段会生成全局的执行上下文,在执行之前先将变量定义和函数声明提前,针对一个函数会生成一个函数执行上下文,在函数执行之前,将函数中的变量定义,函数声明,this,arguments拿出来。
要注意的是函数声明和函数表达式的区别,看代码

function fn () { }          // 函数的声明
var fn = function () { }    // 函数表达式

this

其实本质上来说,js里面的this是一个指向函数执行环境的指针
,this在哪个函数里面,就指向这个函数的context,this永远指向最后调用它的对象,并且this要在执行时才能确认值,定义时无法确认。 例如

var a = {
    name :  "A",
    fn : function () {
        console.log (this.name)
    }
}
a.fn()                          // this === a
a.fn.call ({name : "B"})        // this === {name : "B"},
var fn1 = a.fn
fn1()                           // this === window

第一种情况,是a调用了fn(),所以此时的this为a;第二种情况,使用call(),将this的值指定为了{name : "B"};第三种情况,把a.fn赋给了fn1,但是调用时却是window在调用,所以this === window
this其实有好几种使用场景,在这里我们主要介绍其中四种

  1. 作为构造函数执行
function  Foo (name,age) {
    this.name = name           // this === f
    this.age = age             // this === f
    //return  this
}
var f = new Foo ("zhangsan",20)       

在这里new操作符会改变函数this的指向问题,指向创建的对象f。为什么this会指向f?首先new关键字会创建一个空的对象,然后会自动调用一个函数apply方法,将this指向这个空对象,这样的话函数内部的this就会被这个空的对象替代。

更新一个小问题:若构造函数中返回this或返回值是基本类型(number、string、boolean、null、undefined)的值,则返回新实例对象;若返回值是引用类型的值,则实际返回值为这个引用类型。例如:

function Fn()  
{  
    this.user = 'Jay';  
    return {};  
}
var a = new Fn();  
console.log(a.user);     //undefined

function Fn()  
{  
    this.user = 'Jay';  
    return function () {};
}
var a = new Fn;  
console.log(a.user);    //undefined

function Fn()  
{  
    this.user = 'Jay';  
    return 1;
}
var a = new Fn();  
console.log(a.user);    // Jay

还有一点就是虽然null也是对象,但是在这里this还是指向那个函数的实例,因为null比较特殊。

function Fn()  
{  
    this.user = 'Jay';  
    return null;
}
var a = new Fn;  
console.log(a.user);          // Jay

知识点补充:在严格版中的默认的this不再是window,而是undefined

  1. 作为普通函数执行
function  fn () {
   console.log (this)       // this === window
}
fn ()
  1. 作为对象属性执行
var obj = {
    name : "A",
    printName : function () {
        console.log (this.name)        // this === obj
    }
}
obj.printName ()
  1. call() apply() bind()
    这三种方法都可以改变函数体内部this的指向,但是又存在一些差异。下面,我们看一个例子让你完全搞懂这三个方法的异同点。
var name  = "小明" , age = "17"
var obj = {
    name : "安妮",
    objAge :this.age,
    fun : function () {
        console.log ( this.name + "今年" + this.age )    
    }
}
console.log(obj.objAge)          // 17
obj.fun()                        // 安妮今年undefined

出现这样的结果是因为,在obj.objAge中,this的指向是obj,但是这句话外部没有函数包着,所以这里的执行环境是全局的;在obj.fun()中,this的指向也是obj,但是obj中没有定义age,所以是undefined。再来看第二个例子:

var name1  = "Jay" 
function show () {
    console.log (this.name1)  
} 
show()         // Jay

这个很明显this指向的是window,所以打印的结果就是Jay。
下面我们分别用这三个方法重新定义this,看看是什么结果

var name  = "小明" , age = "17"
var obj = {
    name : "安妮",
    objAge :this.age,
    fun : function () {
        console.log ( this.name + "今年" + this.age )    
    }
}

var a = {
    name : "Jay",
    age : 23
}

obj.fun.call(a)              // Jay今年23
obj.fun.apply(a)             // Jay今年23
obj.fun.bind(a)()            // Jay今年23

我们会发现这三个方法的结果都一致,只有bind返回的是一个新的函数,你必须调用它才会被执行。
再来对比一下传参的情况:

var name  = "小明" , age = "17"
var obj = {
  name : "安妮",
  objAge :this.age,
  fun : function (like,dislike) {
    console.log (this.name + "今年" + this.age ,"喜欢吃" + like + "不喜欢吃" + dislike)   
  }
}

var a = {
    name : "Jay",
    age : 23
}

obj.fun.call(a,"苹果","香蕉")              // Jay今年23 喜欢吃苹果不喜欢吃香蕉
obj.fun.apply(a,["苹果","香蕉"])           // Jay今年23 喜欢吃苹果不喜欢吃香蕉
obj.fun.bind(a,"苹果","香蕉")()            // Jay今年23 喜欢吃苹果不喜欢吃香蕉

call,bind,apply 这三个函数的第一个参数都是 this 的指向对象,注意如果call和apply的第一个参数写的是null,那么this指向的是window对象;第二个参数有区别:① call的参数是直接放进去的,用逗号分隔;② apply的所有参数都必须放在一个数组里面传进去;③ bind除了返回是函数以外,它的参数和call一样。当然,三者的参数不限定是string类型,允许是各种类型,包括函数 、 object 等等。
call和apply都是改变上下文中的this并立即执行这个函数,bind方法可以让对应的函数想什么时候调就什么时候调用,并且可以将参数在执行的时候添加。
在非严格模式下,call和apply的第一个参数如果指定为null或undefined时,会自动指向全局对象。

你可能感兴趣的:(JS三座大山之作用域和闭包(1))