JavaScript基础(作用域、闭包、this)

1 JavaScript作用域与闭包

1.1 编程语言的作用域规则

1.1.1 词法作用域:

词法作用域:定义在词法阶段的作用域。函数和变量的作用域由书写代码时,函数和变量的声明位置决定。

可再细分为函数作用域块作用域

与调用位置无关,与定义的位置有关

示例1:

var a=0;
function f1(){
    var a=1;
    f2();
}

function f2(){
    console.log(a);
}

f1();

实例中的f2函数中的a变量的值,会在定义f2()函数的上层作用域查找a。

即,找到全局作用域中的 a=0,输出结果0

1.1.2 动态作用域:

动态作用域:作用域是基于调用栈的,在代码runtime的时候才决定,灵活多变,但是无法提前准确预知。例如shell语言

故和示例1相同情况的声明和调用,输出结果为1。

因为会进入函数的调用栈,当打印的函数找不到变量值,查找上层f1的调用栈,并在上层调用栈中,查找到对a的赋值。

因此,这种情况下,输出值与调用函数直接相关

1.2 函数作用域与块作用域

都属于语法作用域

①函数作用域(var声明)

函数内定义的变量,在整个函数内有效,附加效果:变量提升,浏览器中全局声明会添加到windows对象。

②块作用域(let const声明)

定义的变量只在变量所在的代码块有效,附加效果:无变量提升+暂时性死区、在for循环中绑定每一个迭代。

let声明的变量可以重新赋值,const不可以。

示例2:

function showVar(){
	if(true){
		var a=1;
	}
	typeof a != 'undefined'?console.log(a):console.log('a is undefined')
}
showVar();

function showLet(){
	if(true){
		let a=1;
	}
	typeof a != 'undefined'?console.log(a):console.log('a is undefined')
}
showLet();

var声明的变量 作用域为函数,故输出1

let只作用于if控制语句的代码块。输出a is undefined

1.3 附加效果

①变量提升(var)

示例3:

function showVar(){
    console.log(a)
	if(true){
		var a=1;
	}
}
showVar();

function showLet(){
    console.log(a)
	if(true){
		let a=1;
	}
}
showLet();

第一个函数输出undefined 变量a已定义,但是未赋值。

等价于var a=1;提升到作用域顶部,

第二个函数输出ReferenceError: a is not defined 报错

typeof 的安全防范机制:使用typeof 访问未定义的变量,可以防止抛出ReferenceError。

②暂时性死区(let const)

let const声明变量时,会在当前的块作用域开始处直到变量声明之前形成暂时性死区,在该区域内,不可引用未定义的变量,即使是typeof安全机制也会失效

示例4:

function showLet(){
	if(true){
		typeof a != 'undefined'?console.log(a):console.log('a is undefined')
		let a=1;
	}
}
showLet();

输出:ReferenceError: Cannot access ‘a’ before initialization

③在for循环中绑定每一次迭代

示例5:

function showVarFor(){
    for(var i=0;i<5;i++){
        setTimeout(()=>{
            console.log(i);
        },i*100);
    }
}
showVarFor();

function showLetFor(){
    for(let i=0;i<5;i++){
        setTimeout(()=>{
            console.log(i);
        },i*100);
    }
}
showLetFor();

var输出:5 5 5 5 5

let输出:0 1 2 3 4

这里设置了五个延时函数,再使用var声明时,设置好五次延时函数,i已经自增到5。再次访问i,输出全是5。let声明会绑定循环的每一次迭代。

可以通过函数传参的形式,将参数传入函数作用域,以复制的形式保存下每次循环的值,故而使用var也可以进行正常使用。

function showVarFor(){
    for(var i=0;i<5;i++){
        function test(j){
            setTimeout(()=>{
            	console.log(j);
        	},j*100);
        }
        test(i);
    }
}
showVarFor();

此时输出:0 1 2 3 4

1.4 闭包

当一个变量“脱离”了声明时所处的作用域而存在时,这个变量就成了闭包变量

闭包的好处:延长变量的生命周期,让函数可以保存状态,并且对外隐藏状态

闭包的应用:定义模块、函数柯里化…

js中闭包无处不在

**闭包的坏处:**变量无法释放,引发内存泄露

**解决:**及时手动释放变量

1.4.1保持内部状态

闭包让函数保持状态,传统面向对象的思路,使用类和实例来保存内部状态。这一点可以利用闭包实现。

function CreatePerson(){
    var myName;
    return{
        setName(name){
            myName=name;
        },
        getName(){
            return myName;
        }
    };
}
person=CreatePerson();
person.setName('james');
console.log(person.getName());
console.log(person.myName);

james

undefined

myName可以看做内部状态

1.4.2 函数柯里化

//计算立方体体积
function calculateVolumeFactory(x,y){
    const area=x*y;
    return function(z){
        return area*z;
    }
}

const calculateVolumeWithArea=calculateVolumeFactory(10,20);
console.log(calculateVolumeWithArea(30));
console.log(calculateVolumeWithArea(40));

6000

8000

1.4.3 利用闭包模拟模块定义

用模块容器存储模块变量,存储每一个模块,通过get得到,进行调用。

//例子三、利用闭包模拟模块定义
const Modules = ( function Manager(){
//闭包变量模块对象容器
var modules = {};
/***
 *@description :定义模块
 *@aparam {string} name模块名
 *@param {string[]} deps模块依赖数组
 *@param : {function} impl 模块实现函数,用来定义模块、创建返回模块对象
*/
function define(name, deps, impl) ,{
    for (var i=0;i< deps.length; i++){
    	if(!modules[deps[i]]){
    		throw new Error( `${modules[deps[i]]} is not defined, canot init ${ name}` );
    }
    deps[i]. = modules [ deps[i]];
    modules[name ]= impl.apply(impl, deps);
}
    
/**
 *@description获取指定模块对象
 *@param {string} name模块名
 *@returns {object} 模块对象
*/
function get(name){ 
}
return {
    define,
	get,
 }
})();

    
    
//定义bar模块
Modules.define( 'bar', []function() {
function hello() { 
	console.log( 'hello' );
}
//返回bar模块对象
return {
	hello,//对外暴露一个hello方法
});

//定义foo模块
Modules .define( 'foo', ['bar'], function(bar) {
function world(){
	bar.hello();
	console.log('world');
}
//返回foo模块对象
return {
world,//对外暴露一个world方法
 };
});
foo.Modules.get('foo');
foo.world();

//手动释放
foo=null;
bar=null;

输出 hello world

2 this指针

并不遵守词法作用域

影响因素:

①方法是否由某个对象调用(method) : obj.test()

②是否是独立函数调用(function) : test()

③是否使用函数的apply、call. bind、 等方法进行硬绑定test.apply(obj);

④是否使用了ES6的箭头函数: ()=>{…}
⑤是否使用了new关键字调用函数: new test()
⑥浏览器中的全局this指针

//放在浏览器中执行

//1全局this指针
console.log(this); //输出window对象

//2作为method调用
var a = {
	logThis(){
		console.log(this);
    }
}
a.logThis(); //输出a
///a的附属方法的调用

//3
var a={
	logThis() {
		console.log(this);
    }
}
var logThis4 = a.logThis;
logThis4();//输出window对象
///相当于一个全局对象的一个调用,不再是a的附属方法的调用,this不再指a

//4作为function调用  不属于严格模式
function logThis() {
	console.log(this);
}
logThis(); //输出window对象

//5严格模式
'use strict';
function logThis2() {
	console.log(this);
}
logThis2(); //输出undefined

//使用apply、 call、bind,
function logThis2(x, y, z) {
	console.log(this, x, y, z);
}
var obj={msg: 'this is obj'};
logThis2.ply(obj,[1, 2, 3]); //输出obj
logThis2.call(obj, 1, 2, 3); //输出obj
var logThis3 = logThis2.bind(obj, 1, 2);
LogThis3(3); //输出obj

//,apply、call、bind函数都可以用来显式指定函数的this指针。其中apply、 call函数会在绑定this指针后,立即调用函数
// apply、 call之间的差别主要在于绑定this之后, 传入参数的方式不同,apply需要使用数组的方式传入参数,call不需要。
// bind除 了绑定this指针, 还可以绑定参数
//apply的这种特性常常被用来做数组解耦,bind的特性则常用来做预置参数 (与函数柯里化有点类似)

//使用箭头函数
var obj2={
	test() {
        console.log(this);//输出obj2  因为test被obj2调用
        var obj={ !
            test2:()=>{
                console.log(this);
            },
            test3(){
                console.log(this);
            }
    	}
        obj.test2();//输出obj2  箭头函数没有自己的this,只能继承外层的this指针,称为this穿透。
        obj.test3();//输出obj    普通函数 作为obj的方法,被调用所以指向obj
	}
}
obj2.test(); //输出

箭头函数没有自己的this,只能继承外层的this指针,称为this穿透。

//new
function logThisk) {
    console.log(this);
}
new logThis(); // 输出logThis对象
LogThis(); // 输出window对象

new关键字会在函数内部创建一个新对象,然后将this指针指向这个新对象

补充

浏览器中,全局中的this指向全局对象 ;node环境下,全局this指向空
作为独立函数调用时,严格模式下绑定到undefined,否则绑定到window
作为某个对象的方法调用时,指向该对象

New VS call、apply、bind VS箭头函数

new、箭头函数 > bind >apply、call

①箭头函数的优先级大于call、apply、 bind, 无法使用call、apply、bind来改变箭头函数的this,
箭头函数不可以与new关键字起使用。 箭头函数没有自己的this指针, 无法作为构造函数

②new的优先级比bind高,井且new”无法与apply和call一起使用。

③bind的优先级比call和apply都要高。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值