关于闭包,
什么是闭包?
闭包就是能够读取其他函数内部变量的函数。如果我们把 闭包 改称做 闭包函数 这样理解起来可能更容易一些。
闭包就是能够读取其他函数内部变量的函数。例如在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引用的是函数据以执行的的环境对象。
函数有四种调用方法
- 独立调用;
- 由调用者调用(即作为对象成员调用);
- 作为构造函数调用;
- 通过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 操作符,会经历以下四个步骤
- 创建一个对象;
- 将构造函数的作用域赋给新对象(this指向这个新对象);
- 执行构造函数中的代码;
- 返回新对象。
// 模拟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是在定义函数时绑定的,不是在执行过程中绑定的。
-
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。
-
箭头函数不能作为构造函数,和new一起会抛出错误。
-
通过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()); // 同上