一、this
一、函数的调用方式决定了 this 的指向不同:
在 JavaScript 中 this
关键字一般指的是 函数调用时 所在的 环境上下文 ,存储了 环境上下文对象的内存地址 ,根据函数的调用的方式不同 ,其 this
会指向不同的对象 ,我们可以通过 this
关键字在函数内部中操作其指向的对象
要注意:
this的绑定和函数声明的位置没有任何关系,只取决于函数的调用位置和调用方式;
this绑定规则有4点:按优先级1到4判断**
在调用中,this一般指向调用它的函数,直接调用全局作用域中的 say 函数的时候等价于 window.say()
因此 ,全局作用域中的 say 函数中的 this
指向的就是 window
对象 。
1.普通函数调用,此时 this 指向 window
function fn() {
console.log(this); // window
}
fn(); // window.fn(),此处默认省略window
2.构造函数调用, 此时 this 指向 实例对象
function Person(age, name) {
this.age = age;
this.name = name
console.log(this) // 此处 this 分别指向 Person 的实例对象 p1 p2
}
var p1 = new Person(18, 'zs')
var p2 = new Person(18, 'ww')
对象方法调用, 此时 this 指向 该方法所属的对象
var obj = {
fn: function () {
console.log(this); // obj
}
}
obj.fn();
通过事件绑定的方法, 此时 this 指向 绑定事件的对象
<body>
<button id="btn">hh</button>
<script>
var oBtn = document.getElementById("btn");
oBtn.onclick = function() {
console.log(this); // btn
}
</script>
</body>
二、构造函数中的this
// 构造函数的定义
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
this.say = function() {
console.log('我的名字是 ' + this.name + ' ,今年' + this.age + '岁');
}
}
// 通过构造函数创建 person 对象
var person = new Person('momo', 18, '男');
person.say(); // 打印:我的名字是 momo ,今年18岁
在使用 new
关键字调用构造函数后 ,会在堆内存空间中创建一个新的对象 ,然后构造函数中的 this
就储存了堆空间这个新的对象内存地址 ,最后会默认返回这个 this
。
三、修改函数中的 this 指向
可以通过 call()
、 apply()
和 bind()
函数,修改函数中 this
指向。
/* =============== call 与 apply ============ */
function say(a, b) {
console.log('我是' + this.value + ' ' + a + ',' + b);
}
var red = {
value: '红色',
redSay: say
};
var green = {
value: '绿色',
greenSay: say
};
red.redSay(1, 2); // 我是红色 1,2
green.greenSay(3, 4); // 我是绿色 3,4
// 将 redSay 函数中的 this 指向改为 green 对象
red.redSay.call(green, 1, 2); // 我是绿色 1,2
red.redSay.apply(green, [3, 4]); // 我是绿色 3,4
apply() 与call()非常相似,不同之处在于提供参数的方式,apply()使用参数数组,而不是参数列表
call(要指向的对象,参数) apply(要指向的对象,数组参数)
对于bind()
函数,看下面这个例子
window.name = 'window';
var person = {
name: 'momo',
say: function(a, b) {
console.log('我的名字是' + this.name + ' ' + a + ',' + b);
}
}
var mSay = person.say;
// 丢失了 person 对象的 this
mSay(1, 2); // 我的名字是window 1, 2
// 重新给 mSay 的 this 绑定为 person 对象
mSay = mSay.bind(person);
// 此时 mSay 中的 this 就是 person 对象了
mSay(1, 2); // 我的名字是momo 1, 2
// 下面的写法与上面等价
mSay.bind(person, 1, 2)();
mSay.bind(person)(1, 2);
使用 bind()
来修改函数的 this
的时候并不会执行该函数 ,而是 返回一个新的函数对象 ,这个新的函数对象中的 this
被修改为了指定的对象 ,其余的函数体内部代码与修改前的一样 。在 ES6 中箭头函数内部的 ``this`` 指向的是箭头函数定义时的上下文对象 ,不由调用它的对象来决定
。
二、闭包
一、概念
闭包是一个对象 ,其存在于内部函数对象中 ,保存了内部函数所使用的外部函数中定义的数据
产生条件:
- 函数嵌套 。
- 内部函数使用了在外部函数中定义的数据 。
- 指执行了外部函数 。
流程:首先在一个 函数嵌套 的场景下 ,并且 内部函数使用了外部函数定义的数据 ,然后再 执行外部函数 ,当代码执行到 内部函数定义完毕 时 ,此时内部函数中就已经生成了一个闭包对象 ,其 存储了内部函数使用的外部函数中定义的数据
function fn1() {
var a = 3;
// 当 fn2 函数对象定义完毕时 ,其内部产生了闭包对象
function fn2() {
console.log(a);
}
return fn2;
}
// 调用 fn1 函数 ,将 fn2 函数对象的内存地址赋值给 fn3 对象
var fn3 = fn1();
// 中断 fn3 于 fn2 对象之间的引用 ,fn2 被 GC 回收 ,闭包对象死亡
fn3 = null;
闭包的死亡:
在 堆区的内部函数对象没有被栈区的变量引用 时 ,此时堆区的内部函数对象就会被 GC 当作垃圾数据回收 ,同时存在于内部函数对象中的闭包对象就会死亡 。
二、应用
延长了局部数据的存活时间
function fn1() {
let a = 3;
function fn2() {
a++;
console.log(a);
}
return fn2;
}
var fn = fn1();
fn(); // 4
fn(); // 5
在 fn1 函数执行完毕后 ,其内部的局部变量 a 已经被释放 ,但是由于闭包机制的存在 ,fn2 函数对象保存了这个局部变量的数据 ,延长了局部数据的存活时间