面向对象基础知识点汇总(2)

111 阅读8分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天,点击查看活动详情

六、上下文

6-1、认识函数中的上下文

函数中可以使用this关键字,它表示函数的上下文,函数中的this具体指代什么必须通过调用函数时的“前言后语”来判断

来看一个小例子:

var obj = {
  name: '张三',
  age: 18,
  sex: '男',
  sayHello: function() {
    console.log('你好,我是' + this.name + '我今年'+ this.age + '岁')
  }
}
obj.sayHello() // 你好,我是张三我今年18岁

再来看一个例子:

var obj = {
  name: '张三',
  age: 18,
  sex: '男',
  sayHello: function() {
    console.log('你好,我是' + this.name + '我今年'+ this.age + '岁')
  }
}
var sayHello = obj.sayHello;
sayHello();
  • 此处将函数单独“提”出来了,而不是通过对象+ 点的方式调用函数,而是直接通过圆括号调用此函数
  • 此时会输出 你好,我是undefined我今年undefined岁
  • 此处this则不会指代obj对象,而是指代window对象,由于window对象中没有name与age变量,所以为undefined

通过以上两个例子可以得出:

  • 函数的上下文由调用方式决定,通过一个函数,用不同的形式调用,则函数的上下文不同

6-2、上下文绑定规则

函数的上下文(this关键字)由调用函数的方式决定,如果函数不被调用则不能确定函数的上下文。

6-2-1、规则1

对象打点调用它的方法函数( 对象.方法() ),则函数的上下文是这个打点的对象

6-2-1-1、案例1
function fn() {
  console.log(this.a + this.b)
}
var obj = {
  a: 1,
  b: 2,
  fn: fn
};

obj.fn(); // 3

函数上下文是在函数被调用时才能被决定的,fn是由obj打点调用的,所以this指向obj,那么this.a为1,this.b为2,结果为3

6-2-1-2、案例2
var obj1 = {
  a: 1,
  b: 2,
  fn: function () {
      console.log(this.a + this.b);
  }
};
var obj2 = {
  a: 3,
  b: 4,
  fn: obj1.fn
};
obj2.fn(); // 7

虽然obj2的fn被赋值为obj1的fn,但是函数的上下文是调用时才决定的,由于函数fn最终是由obj2打点调用的所以this指向obj2:this.a为3,this.b为4,结果为7

6-2-1-3、案例3
function outer() {
  var a = 11;
  var b = 22;
  return {
      a: 33,
      b: 44,
      fn: function () {
          console.log(this.a + this.b);
      }
  };
}
outer().fn(); // 77

调用outer方法返回一个对象,所以outer().fn()相当于对象打点调取fn,适用此规则,所以this.a为33,this.b为44,最终输出77

6-2-1-4、案例4
function fun() {
  console.log(this.a + this.b)
}
var obj = {
  a: 1,
  b: 2,
  c: [{
      a: 3,
      b: 4,
      c: fun
  }]
};
var a = 5;
obj.c[0].c();//7

c方法的调用是由obj.c[0]打点调用的,而obj.c[0]为一个对象,随意this指向此对象:this.a为3,this.b为4,所以结果为7

6-2-2、规则2

使用圆括号直接调用函数,则函数的上下文是window对象

6-2-2-1、案例1
var obj1 = {
  a: 1,
  b: 2,
  fn: function () {
      console.log(this.a + this.b);
  }
};
var a = 3;
var b = 4;
var fn = obj1.fn;
fn(); // 7

这里,fn变量保存的是obj1中fn函数的引用,最终fn是直接使用圆括号调用的,上下文是window对象,所以this.a为3,this.b为4,结果为7

6-2-2-1、案例2
function fun() {
    return this.a + this.b;
}
var a = 1;
var b = 2;
var obj = {
    a: 3,
    b: fun(),
    fun: fun
};
var result = obj.fun();
console.log(result); // 6

obj中的属性b的值是通过直接调用fun()得到的,这里fun直接使用圆括号调用,上下文为window,所以this.a + this.b为1+2,b的值为3;而result的值是通过obj打点调用fun获取的,这里this指向obj对象,所以this.a + this.b 为3 + 3,最终结果为6

6-2-3、规则3

调用数组(类数组对象)枚举出的函数,那么上下文对象就是这个数组(类数组对象)。

6-2-3-1、案例1
var arr = ['A', 'B', 'C', function () {
    console.log(this[0]);
}];
arr[3](); // A

此处调用的是arr[3]的函数,符合此规则,this指向该数组,所以this[0]为A

6-2-3-2、案例2
function fun() {
  arguments[3]();
}
fun('A', 'B', 'C', function () {
  console.log(this[1]) // B
});

由于调用的是arguments类数组对象中枚举出的函数,那么this指向该类数组对象,所以函数中输出的this[1]即类数组中的索引为1的内容,所以是B

6-2-4、规则4

IIFE中的函数,上下文是window对象

6-2-4-1、案例
  var a = 1;
  var obj = {
    a: 2,
    fun: (function () {
      var a = this.a;
      return function () {
        console.log(a + this.a);
      }
    })()
  };
  obj.fun(); // 3

obj对象中的fun属性对应的是立即执行函数,会立即执行,由于IIFE中的函数上下文是window对象,所以其中的var a = this.a; 即:var a = 1;该函数执行后返回一个函数。所以obj.fun即返回的这个函数,又因为这个函数的执行是通过obj打点调用执行的(适用于规则1),所以这个函数的this.a即2,那么console.log中的内容即 1 + 2 ,所以最后输出3

6-2-5、规则5

定时器、延时器中的回调函数,上下文是window对象

6-2-5-1、案例1
var obj = {
    a: 1,
    b: 2,
    fun: function () {
            console.log(this.a + this.b);
    }
};
var a = 3;
var b = 4;
setTimeout(obj.fun, 2000); // 7

setTimeout中的函数是obj中的fun属性对应的函数,2秒后会由延时器调用,由于延时器中的函数的上下文是window对象,所以this.a+ this.b即3+4,最后结果为7

6-2-5-2、案例2
var obj = {
    a: 1,
    b: 2,
    fun: function () {
            console.log(this.a + this.b);
    }
};
var a = 3;
var b = 4;
setTimeout(function () {
        obj.fun();
}, 2000); // 3

注意,这里与上面不同的是,setTimeout中传递的是一个匿名函数,该匿名函数中obj对象打点调用了fun对应的函数,所以fun函数中this.a+this.b即 1+2,只不过2秒后才会执行匿名函数。结果为3

6-2-6、规则6

事件处理函数的上下文是绑定事件的DOM元素

6-2-6-1、案例1

点击哪个盒子 ,哪个盒子变红

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
      div{
        width: 200px;
        height: 200px;
        float: left;
        border: 1px solid #000;
        margin-right: 10px;
      }
    </style>
  </head>
  <body>
    <div id="box1"></div>
    <div id="box2"></div>
    <div id="box3"></div>
    
    <script>
      function setColorToRed() {
        this.style.backgroundColor = 'red';
      }
      
      var box1 = document.getElementById('box1');
      var box2 = document.getElementById('box2');
      var box3 = document.getElementById('box3');
      
      box1.onclick = setColorToRed;
      box2.onclick = setColorToRed;
      box3.onclick = setColorToRed;
    </script>
  </body>
</html>
6-2-6-2、案例2

点击哪个盒子,哪个盒子两秒后变红

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
      div{
          width: 200px;
          height: 200px;
          float: left;
          border: 1px solid #000;
          margin-right: 10px;
      }
  </style>
</head>
<body>
  <div id="box1"></div>
  <div id="box2"></div>
  <div id="box3"></div>

  <script>
      function setColorToRed() {
          // 备份上下文
          var self = this;
          setTimeout(function() {
              self.style.backgroundColor = 'red';
          }, 2000);
      }

      var box1 = document.getElementById('box1');
      var box2 = document.getElementById('box2');
      var box3 = document.getElementById('box3');

      box1.onclick = setColorToRed;
      box2.onclick = setColorToRed;
      box3.onclick = setColorToRed;
  </script>
</body>
</html>

要注意,由于有2秒的延迟,需要通过setTimeout调用该函数,但是setTimeout中函数的上下文是window,所以要备份一下上下文。(所以我们在书写this的时候要留意一下,this会不会被其他地方的逻辑所改变)

七、call与apply

使用call与apply可以指定函数的上下文:

function sum() {
  console.log(this.c + this.m + this.e);
};

var zhangsan = {
  c: 100,
  m: 90,
  e: 80
};

sum.call(zhangsan);
sum.apply(zhangsan);

以上案例,通过使用call/apply,将sum函数的上下文指定成为了zhangsan对象

7-1、call与apply的区别

call与apply的区别在接收参数的时候才能体现出来:

  • call接收参数是以逗号分隔罗列开来
  • apply接收参数是以数组接收
function sum(b1, b2) {
    alert(this.c + this.m + this.e + b1 + b2);
};

var zhangsan = {
    c: 100,
    m: 90,
    e: 80
};

sum.call(zhangsan, 66, 88);
sum.apply(zhangsan, [66, 88]);

7-2、上下文总结

规则上下文
对象.函数()对象
函数()window
数组下标数组
IIFEwindow
定时器window
DOM事件处理函数绑定DOM的元素
call和apply任意指定

八、构造函数

8-1、用new操作符来调用函数

new 函数();

JS规定,使用new操作符调用函数会进行“四步走”:

1)函数体内会自动创建出一个空白对象

2)函数的上下文(this)会指向这个对象

3)执行函数体内的语句

4)自动返回上下文对象,即使函数没有return语句

比如,现有以下代码:

function fun() {
  this.a = 3;
  this.b = 6;
}

var obj = new fun();
console.log(obj);

第一步:函数体内创建空白对象

第二步:函数上下文this指向该对象

第三步:执行函数体内语句

则此时会为该空白对象添加上a与b属性

第四步:自动返回该对象

至此可见,上下文绑定规则便多了一条:

规则上下文
对象.函数()对象
函数()window
数组下标数组
IIFEwindow
定时器window
DOM事件处理函数绑定DOM的元素
call和apply任意指定
new调用函数创建出来的对象

8-2、什么是构造函数

我们先来看以下代码:

		function People(name, age, sex) {
			this.name = name;
			this.age = age;
			this.sex = sex;
		}

		var xiaoming = new People('小明', 12, '男');
		var xiaohong = new People('小红', 4, '女');
		var xiaogang = new People('小刚', 17, '男');

		console.log(xiaoming);
		console.log(xiaohong);
		console.log(xiaogang);

我们在通过new关键字来调取People函数时传了三个函数来构造出了三个不同的对象,为这三个对象都添加了name、age、sex属性。

  • 用new调用一个函数,这个函数就被称为“构造函数”,任何函数都可以是构造函数,只需要用new调用它
  • 构造函数用来“构造新对象”,它内部的语句将为新对象添加若干属性和方法,完成对象的初始化
  • 构造函数必须用new关键字调用,否则不能正常工作(相当于独立调用函数,this指向window全局对象),开发者约定构造函数命名时首字母要大写

并不是一个函数名称首字母大写了它就是构造函数,而是要看其是否用new调用了

8-3、为对象添加方法

除此之外,我们还可以为构造出来的对象添加方法:

function People(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
    this.sayHello = function () {
            console.log('你好,我是' + this.name + ',我今年' + this.age + '岁了,我是个' + this.sex + '生');
    };
    this.sleep = function () {
            console.log(this.name + '开始睡觉,zzzzz');
    };
}

var xiaoming = new People('小明', 12, '男');
var xiaohong = new People('小红', 4, '女');
var xiaogang = new People('小刚', 17, '男');

console.log(xiaoming);
console.log(xiaohong);
console.log(xiaogang);

xiaohong.sayHello();
xiaogang.sleep();

这样,三个不同的对象都会拥有sayHello与sleep方法