一、this

一、函数的调用方式决定了 this 的指向不同:

在 JavaScript 中 this 关键字一般指的是 函数调用时 所在的 环境上下文 ,存储了 环境上下文对象的内存地址 ,根据函数的调用的方式不同 ,其 this 会指向不同的对象 ,我们可以通过 this 关键字在函数内部中操作其指向的对象
要注意:
this的绑定和函数声明的位置没有任何关系,只取决于函数的调用位置和调用方式;
this绑定规则有4点:按优先级1到4判断**
在调用中,this一般指向调用它的函数,直接调用全局作用域中的 say 函数的时候等价于 window.say() 因此 ,全局作用域中的 say 函数中的 this 指向的就是 window 对象 。
1.普通函数调用,此时 this 指向 window

  1. function fn() {
  2. console.log(this); // window
  3. }
  4. fn(); // window.fn(),此处默认省略window

2.构造函数调用, 此时 this 指向 实例对象

  1. function Person(age, name) {
  2. this.age = age;
  3. this.name = name
  4. console.log(this) // 此处 this 分别指向 Person 的实例对象 p1 p2
  5. }
  6. var p1 = new Person(18, 'zs')
  7. var p2 = new Person(18, 'ww')

对象方法调用, 此时 this 指向 该方法所属的对象

  1. var obj = {
  2. fn: function () {
  3. console.log(this); // obj
  4. }
  5. }
  6. obj.fn();

通过事件绑定的方法, 此时 this 指向 绑定事件的对象

  1. <body>
  2. <button id="btn">hh</button>
  3. <script>
  4. var oBtn = document.getElementById("btn");
  5. oBtn.onclick = function() {
  6. console.log(this); // btn
  7. }
  8. </script>
  9. </body>


二、构造函数中的this

  1. // 构造函数的定义
  2. function Person(name, age, gender) {
  3. this.name = name;
  4. this.age = age;
  5. this.gender = gender;
  6. this.say = function() {
  7. console.log('我的名字是 ' + this.name + ' ,今年' + this.age + '岁');
  8. }
  9. }
  10. // 通过构造函数创建 person 对象
  11. var person = new Person('momo', 18, '男');
  12. person.say(); // 打印:我的名字是 momo ,今年18岁

在使用 new 关键字调用构造函数后 ,会在堆内存空间中创建一个新的对象 ,然后构造函数中的 this 就储存了堆空间这个新的对象内存地址 ,最后会默认返回这个 this

三、修改函数中的 this 指向

可以通过 call()apply()bind() 函数,修改函数中 this 指向。

  1. /* =============== call 与 apply ============ */
  2. function say(a, b) {
  3. console.log('我是' + this.value + ' ' + a + ',' + b);
  4. }
  5. var red = {
  6. value: '红色',
  7. redSay: say
  8. };
  9. var green = {
  10. value: '绿色',
  11. greenSay: say
  12. };
  13. red.redSay(1, 2); // 我是红色 1,2
  14. green.greenSay(3, 4); // 我是绿色 3,4
  15. // 将 redSay 函数中的 this 指向改为 green 对象
  16. red.redSay.call(green, 1, 2); // 我是绿色 1,2
  17. red.redSay.apply(green, [3, 4]); // 我是绿色 3,4

apply() 与call()非常相似,不同之处在于提供参数的方式,apply()使用参数数组,而不是参数列表

call(要指向的对象,参数) apply(要指向的对象,数组参数)

对于bind() 函数,看下面这个例子

  1. window.name = 'window';
  2. var person = {
  3. name: 'momo',
  4. say: function(a, b) {
  5. console.log('我的名字是' + this.name + ' ' + a + ',' + b);
  6. }
  7. }
  8. var mSay = person.say;
  9. // 丢失了 person 对象的 this
  10. mSay(1, 2); // 我的名字是window 1, 2
  11. // 重新给 mSay 的 this 绑定为 person 对象
  12. mSay = mSay.bind(person);
  13. // 此时 mSay 中的 this 就是 person 对象了
  14. mSay(1, 2); // 我的名字是momo 1, 2
  15. // 下面的写法与上面等价
  16. mSay.bind(person, 1, 2)();
  17. mSay.bind(person)(1, 2);

使用 bind() 来修改函数的 this 的时候并不会执行该函数 ,而是 返回一个新的函数对象 ,这个新的函数对象中的 this 被修改为了指定的对象 ,其余的函数体内部代码与修改前的一样 。
在 ES6 中箭头函数内部的 ``this`` 指向的是箭头函数定义时的上下文对象 ,不由调用它的对象来决定

二、闭包

一、概念

闭包是一个对象 ,其存在于内部函数对象中 ,保存了内部函数所使用的外部函数中定义的数据
产生条件:

  • 函数嵌套 。
  • 内部函数使用了在外部函数中定义的数据 。
  • 指执行了外部函数 。

流程:首先在一个 函数嵌套 的场景下 ,并且 内部函数使用了外部函数定义的数据 ,然后再 执行外部函数 ,当代码执行到 内部函数定义完毕 时 ,此时内部函数中就已经生成了一个闭包对象 ,其 存储了内部函数使用的外部函数中定义的数据

  1. function fn1() {
  2. var a = 3;
  3. // 当 fn2 函数对象定义完毕时 ,其内部产生了闭包对象
  4. function fn2() {
  5. console.log(a);
  6. }
  7. return fn2;
  8. }
  9. // 调用 fn1 函数 ,将 fn2 函数对象的内存地址赋值给 fn3 对象
  10. var fn3 = fn1();
  11. // 中断 fn3 于 fn2 对象之间的引用 ,fn2 被 GC 回收 ,闭包对象死亡
  12. fn3 = null;

闭包的死亡:
在 堆区的内部函数对象没有被栈区的变量引用 时 ,此时堆区的内部函数对象就会被 GC 当作垃圾数据回收 ,同时存在于内部函数对象中的闭包对象就会死亡 。

二、应用

延长了局部数据的存活时间

  1. function fn1() {
  2. let a = 3;
  3. function fn2() {
  4. a++;
  5. console.log(a);
  6. }
  7. return fn2;
  8. }
  9. var fn = fn1();
  10. fn(); // 4
  11. fn(); // 5

在 fn1 函数执行完毕后 ,其内部的局部变量 a 已经被释放 ,但是由于闭包机制的存在 ,fn2 函数对象保存了这个局部变量的数据 ,延长了局部数据的存活时间