JavaScript闭包+函数内部的this指向

关于闭包,

  什么是闭包?
  闭包就是能够读取其他函数内部变量的函数。如果我们把 闭包 改称做 闭包函数 这样理解起来可能更容易一些。

闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。什么是闭包

真正的定义
闭包————英文连接
闭包————中文连接

A closure is the combination of a function and the lexical environment within which that function was declared.
译文:
闭包是函数和声明该函数的词法环境的组合。

如果说对象是有行为的数据,那么闭包可以理解为有数据的行为,

闭包是个函数,而它「记住了周围发生了什么」。表现为由「一个函数」体中定义了「另个函数」。

function test(){
    const m = 12;
    return function(){
        console.log(m);
    }
}
const j = test();
j(); // 12; // 这里访问到了test内部的变量

闭包会携带包含它的函数的作用域因此它会占据跟多的内存。所以要慎重使用。

函数内部的this指向

this引用的是函数据以执行的的环境对象。

函数有四种调用方法

  1. 独立调用;
  2. 由调用者调用(即作为对象成员调用);
  3. 作为构造函数调用;
  4. 通过call/apply/bind调用(这也算一种调用方法吧)

独立调用的时候其内部this为undefined,(非严格模式下为全局(window/global));
由调用者调用的时候,其内部this指向调用者。
作为构造函数调用 其内部的this指向 可以理解为该函数的 prototype属性
通过call/apply/bind调用其内部this指向指定值(非严格模式下如果指定了null,那么会指向全局)

"use strict" // 严格模式
var a = 123;
var m = {
	a:12
};
function test(){
	console.log(this?.a);
	if(this === undefined){
	    console.log('this is undefined:',this)
	}
	if(this === window){
	    console.log('this is window:',this)    
	}
	if(this instanceof test){
        console.log('this is test:',this)
	}
}
test.prototype.a = 55;
m.test = test;
test();   // 独立调用 this 指向undefined,a 自然也是undefined了。如果是非严格模式下this指向window,可以视为在非严格模式下,没有独立调用,而是隐式的window.test()方式调用的。
window.test(); // 调用者调用 this指向window,a 就等于window上了a 也就是 123了。
m.test() // 调用者调用 指向m,所以a 就是m上的a,也就是 12;
new test(); // 作为构造函数调用 this指向 Object.create(test.prototype),所以a就是 55了。 

改变this指向

可以通过 call apply bind 来手动显式指定函数的this指向,很大一部分原因就是这几个方法让this指向变得迷惑的。

call apply

例如上面的例子,我们想要使用方法test输出对象m的属性a,就要把test函数加到对象m上,这样就改变了对象m。而通过这三个函数则可以在不改变对象m的情况下,获得属性a的值。

call 和 apply 作用相同,就是在特定的作用域中调用函数,区别是apply接受两个参数,第一个是运行函数的作用域(也就是this的值),第二个是传递给函数的参数数组,call的第一个参数和apply相同,不同是其余参数直接传递给函数。

xx.apply(this,[a,b,c])xx.call(this,a,b,c) 调用的结果是相同的。

"use strict"
var m = {
	a:12
};
function test(q){
	console.log(this?.a);
	console.log(q)
}

test.apply(m,[13]);   // 12 ,13  
test.call(m,14) ;  // 12 , 14

call 和apply以指定this的方法调用了一次函数,我们自己写函数的时候尽量显式传递需要指定的值,这样看起来更清晰,代码更易读,也可以兼容箭头函数,否则很容易写出让人看不懂的代码的。

"use strict"
var m = {
	a:12
};
function test(that,q){
	console.log(that?.a);
	console.log(q)
}

test(m,15); // 12, 15

bind

bind方法则是创建一个新的函数实例,而这个新的函数实例的this会绑定为指定的值,绑定this的同时也可指定参数。

"use strict"
var m = {
	a:12
};
function test(){
	console.log(this);
	console.log(this?.a)
}

var bindTest = test.bind(m);
bindTest(); // 此时是独立调用,可其内部this仍然指向m;

bind方法返回的函数,通过独立调用的时候this指向指定的值,通过调用者调用的时候this的指向也不会改变依然指向指定的值,而不是指向调用者。再次使用call apply bind也无法改变其this指向了,这三个函数传递的第一个参数将会被忽略。
作为构造函数调用的时候,this 依旧指向该函数的 prototype属性,(指向Object.create(test.prototype),不是bindTest,bindTest 没有prototype)

  由于apply接受的时候参数数组,内部需要以判断参数以及转换什么的,所以说部分环境中相对call较慢;call接受的参数和函数的参数是一样的不用转换和判断,所以优先使用call速度快些,原生bind方法会调用instanceof访问原型链,速度会更慢。

所以很多框架都会实现自己的简化的bind方法,下面的就是Vue内部自己实现的的bind。

/**
 * Simple bind, faster than native
 */
function bind (fn, ctx) {
  function boundFn (a) {
    var l = arguments.length;
    return l
      ? l > 1
        ? fn.apply(ctx, arguments)
        : fn.call(ctx, a)
      : fn.call(ctx)
  }
  // record original fn length
  boundFn._length = fn.length;
  return boundFn
}

构造函数中的this

函数作为构造函数调用,需要使用new 操作符,会经历以下四个步骤

  1. 创建一个对象;
  2. 将构造函数的作用域赋给新对象(this指向这个新对象);
  3. 执行构造函数中的代码;
  4. 返回新对象。

// 模拟new操作符

function newObj(fn, ...args) {
  // 创建一个空对象
  let obj = new Object()
  // 将newObj的原型指向构造函数的prototype 
  obj.__proto__ = fn.prototype
  // 上面可以合并为  let obj = Object.create(fn.prototype);
  // 将newObj的this指向obj
  const result = fn.apply(obj, args)
  // 依据返回判断值 如果返回值是对象就返回这个对象 返回值不是对象 则返回创建的对象
  return typeof result === 'object' ? result : obj;
}

箭头函数里的this

定义:箭头函数的this是在定义函数时绑定的,不是在执行过程中绑定的。

  1. 箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。

  2. 箭头函数不能作为构造函数,和new一起会抛出错误。

  3. 通过call apply bind 调用的时候也不会影响其this指向,传递的第一个参数会被忽略。

'use strict';
var obj = {
  i: 10,
  b: () => console.log(this.i, this),
  c: function() {
    console.log( this.i, this)
  }
}
obj.b(); 
// undefined, Window{...}
obj.c(); 
// 10, Object {...}

一个通过function声明的函数,

当函数被声明之后,它的作用域链就是确定的了,那么它能访问哪些作用域自然也是确定的不可改变的了。
但是它的this指向并没有确定(其默认值非严格模式下指向window,严格模式下其指向null),可用通过call/apply/bind/new 或者将其加到对象上,来改变其内部的this指向。

箭头函数:

当函数被声明之后,它的作用域链就是确定的了,那么它能访问哪些作用域自然也是确定的不可改变的了。这和通过function声明的函数是一致的。
区别在于,在声明的时候其this指向也是确定的不可改变的(指向作用域链上层的this),无论是什么方法调用。也是因为这个原因箭头函数不能当构造函数。

function Pet(name) {
  this.name = name;

  this.getName = () => this.name;
}

const cat = new Pet('Fluffy');

console.log(cat.getName()); // Fluffy

const { getName } = cat;
console.log(getName());     // Fluffy 此时虽然是单独调用但是箭头函数的this是固定的
const dog = {
	name: 'dog',
	getName,
};
console.log(dog.getName()); // 同上

文章

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值