this / 作用域 / 闭包

一、专业术语

  • 常量、变量、数据类型
  • 形参、实参
  • 匿名函数、具名函数、自执行函数
  • 函数声明、函数表达式
  • 堆、栈
  • 同步、异步、进程、线程

1. 执行上下文

当函数执行时,会创建一个称为执行上下文(execution contex)的环境,分为创建和执行2个阶段。

2. 创建阶段

创建阶段,指函数被调用但还未执行任何代码时,此时创建了一个拥有3个属性的对象:

executionContext = {
	scopeChain: {}, // 创建作用域链(scope chain) 
	variableObject: {}, // 初始化变量、函数、形参 
	this: {} // 指定this
}

3. 代码执行阶段

代码执行阶段主要的工作是:

  1. 分配变量、函数的引用,赋值
  2. 执行代码

举个栗子

// 一段这样的代码 
function demo(num) {
	var name = 'xiaowa';
	var getData = function getData() {}; 
	function c() {}
} 
demo(100);

// 创建阶段大致这样,在这个阶段就出现了【变量提升(Hoisting)】 
executionContext = {
	scopeChain: { ... }, 
	variableObject: {
 		arguments: {
   			0: 100,
			length: 1 
		},
    	num: 100, //
		c: pointer to function c(), // 有内部函数声明的话,创建引用指向函数体
		name: undefined, // 有内部声明变量a,初始化为undefined 	
		getData: undefined // 有内部声明变量b,初始化为undefined
   	},
	this: { ... } 
}


// 代码执行阶段,在这个阶段主要是赋值并执行代码 
executionContext = {
	scopeChain: { ... }, 
	variableObject: {
        arguments: {
            0: 100,
			length: 1 
		},
		num: 100,
		c: pointer to function c(),
		name: 'xiaowa', // 分配变量,赋值
		getData: pointer to function getData() // 分配函数的引用,赋值
	},
	this: { ... } 
}

4. 执行上下文栈

  • 浏览器中的JS解释器是单线程的,相当于浏览器中同一时间只能做一个事情。
  • 代码中只有一个全局执行上下文,和无数个函数执行上下文,这些组成了执行上下文栈 (Execution Stack)。
  • 一个函数的执行上下文,在函数执行完毕后,会被移出执行上下文栈。

举个栗子

function c(){ 
 	console.log('ok');
}
function a(){
    function b(){
        c();
	}
	b(); 
}

a();

这个栗子的执行上下文栈是这样的
在这里插入图片描述

二、 作用域

js中有全局作用域、函数作用域,es6中又增加了块级作用域。作用域的最大用途就是隔离变量或函 数,并控制他们的生命周期。作用域是在函数执行上下文创建时定义好的,不是函数执行时定义的。

举个栗子

// 不要看晕了哦~ 
function a () {
	return function b() {
		var myname = 'b'; 
		console.log(myname); // b
	} 
}

function c() {
	var myname = 'c'; 
	b();
}

var b = a(); 
c();

// 去掉函数b中的myname声明后 
function a () {
	return function b() {
		// var myname = 'b'; 
		console.log(myname); // 这里会报错
	} 
}

function c() {
	var myname = 'c'; b();
}

var b = a(); 
c();

三、 作用域链

当一个块或函数嵌套在另一个块或函数中时,就发生了作用域的嵌套。在当前函数中如果js引擎无法找 到某个变量,就会往上一级嵌套的作用域中去寻找,直到找到该变量或抵达全局作用域,这样的链式 关系就称为作用域链(Scope Chain)。

作用域链只和定义有关 和 函数的执行调用栈没有关系。

四、 闭包

高级程序设计3 中:闭包是指有权访问另外一个函数作用域中的变量的函数,可以理解为(能够读取其他函数内部变量的函数)

 function outer() { 
 	var top = xxxx;
	function inner() { 
		xxx.innerHTML = top;
	} 
}
  • 闭包是指有权限访问另外一个函数作用域中的变量的函数。

  • 闭包可以实现真正的私有变量。

  • 闭包可以用于节流的处理。

  • 闭包可以用于存在缓存的变量。(同一个函数中尽量返回相同类型的数据)

平时用在哪儿?

  1. 封装私有变量(amd的框架等都使用)
    // 普通的定义类的方式 
    function Person() {
    	this._attackVolume = 100; 
    }
    Person.prototype = { 
    	attack(body) {
    		body.bloodVolume -= this.attackVolume - body.defenseVolume; 
    	}
    };
    
    var person = new Person(); 
    console.log(person._attackVolume);
    
    
    // 工厂方法
    function Person() {
    	var _attackVolume = 100; 
    	return {
    		attack() {
    			body.bloodVolume -= this.attackVolume - body.defenseVolume;
    		} 
    	};
    }
    
    var person = new Person(); 
    console.log(person._attackVolume);
    
    
  2. 存储变量
    // 封装的时候
    function getListDataManager() {
    	// 外层scope中定义一个变量 
    	let localData = null;
    	return {
            getData() {
      			// 里面的函数使用外层的变量,而且是反复使用 
      			if (localData) {
    				return Promise.resolve(localData); 
    			}
    			return fetch('xxxx')
    				.then(data => localData = data.json());
           	} 
         };
    }
    
    
    // 用的时候
    const listDataManager = getListDataManager();
    button.onclick = () => {
    	// 每次都会去获取数据,但是有可能是获取的缓存的数据 
    	text.innerHTML = listDataManager.getData();
    };
    
    window.onscroll = () => {
    	// 每次都会去获取数据,但是有可能是获取的缓存的数据 
    	text.innerHTML = listDataManager.getData();
    };
    
    

五、 this

一共有5种场景

场景1: 函数直接调用时

function myfunc() { 
 	console.log(this) // this是widow
}

var a = 1; 
myfunc();

场景2: 函数被别人调用时

function myfunc() { 
	console.log(this) // this是对象a
}

var a = {
    myfunc: myfunc
};

a.myfunc();

场景3: new一个实例时

function Person(name) {
	this.name = name; 
	console.log(this); // this是指实例p
}

var p = new Person('zhaowa');

场景4: apply、call、bind时

function getColor(color) { 
	this.color = color; 
	console.log(this);
}

function Car(name, color){
	this.name = name; // this指的是实例car
	getColor.call(this, color); // 这里的this从原本的getColor,变成了car
}

var car = new Car('卡⻋', '绿色');

场景5: 箭头函数时

// 复习一下场景1 
var a = {
    myfunc: function() {
        setTimeout(function(){
			console.log(this); 
		}, 0)
	} 
};
a.myfunc();

// 稍微改变一下 
var a = {
	myfunc: function() {
		var that = this; 
		setTimeout(function(){
			console.log(that); 
		}, 0)
	} 
};
a.myfunc();

// 箭头函数 
var a = {
	myfunc: function() { 
		setTimeout(() => {
			console.log(this); 
		}, 0)
	} 
};
a.myfunc();

总结一下

在这里插入图片描述

  1. 对于直接调用的函数来说,不管函数被放在了什么地方,this 都是 window
  2. 对于被别人调用的函数来说,被谁点出来的,this 就是谁
  3. 在构造函数中,类中(函数体中)出现的 this.xxx = xxx 中的 this 是当前类的一个实例
  4. callapply 时,this 是第一个参数。bind 要优与 call / apply 哦,call 参数多,apply 参数少
  5. 箭头函数没有自己的 this ,需要看其外层的是否有函数,如果有,外层函数的 this 就是内部箭头函数的 this ,如果没有,则 thiswindow;箭头函数中的 this 指向定义函数的上一层作用域中的 this

this 指向三板斧

  • 自然执行时,就是 windowundefined(严格模式)
  • 执行时,被谁就是谁(最近的)
  • 执行时,new 的时候,是 new 出来的对象

不要与作用域链搞混,作用域链是找定义时的,this 指向是找执行时

扩展

  • 逗号表达式 var a = (1, 2);,返回最后一个值,但 thiswindow
    var a = (1, 2);  // window
    (1, wife.buy)(); // window,这是立即执行函数
    
  • bind > new > apply、call > methods > 默认
    bind: 不会立即执行,是返回的一个函数,不会改变原函数
    apply:会立即执行,参数是 (xx, [ ])
    call:会立即执行,参数是 (xx, arg1, arg2...)

    口诀:call 打电话一个给一个打
    () => {} :箭头函数六亲不认,手动改变 this 指向无效

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值