必要知识准备
介绍 this 之前,先简单介绍 四个概念(普通函数、对象方法、构造函数、箭头函数),作为我们的知识储备,为后面我们介绍 this 做铺垫
**普通函数:**函数是一段可以被重复调用的代码块,普通函数的声明方法基本结构很简单,我们都知道,就是下面的这种
1
2
3
4
5function 函数名() {
函数体
}
// 调用函数
函数名()对象方法
什么是对象?
- 概念:对象也是 JS 数据类型的一种,和之前学习的数值类型、字符串类型、布尔类型是一样的。对象数据类型可以被理解成是一组 无序的键值对的 集合,是属性的容器
- 键: 又称属性名,通常是一个字符串
- 值: 可以是任何数据类型,包括:数字、字符串、布尔值、数组、函数,甚至是另一个对象(嵌套)
- 属性:属性是成对出现的,包括属性名和属性值,它们之间使用英文
:分隔,多个属性之间使用英文,分隔
怎样创建一个对象呢?
- 最常用的一种就是使用花括号
{ }直接创建
1
2
3
4
5
6
7let 对象名 = {
属性名1: 属性值1,
属性名2: 属性值2,
属性名3: 属性值3,
属性名4: 函数,
...
}什么是方法?
- 属性值可以是任何数据类型,这里面有一个数据类型比较特殊,就是函数,如果一个属性的值是一个函数,那么我们称这个属性是这个对象的一个方法
由于普通函数和方法之间有点像,所以我们举个例子带大家区分一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 创建一个函数
function fn() {
console.log('111')
}
// 直接放在script标签里面,他就是一个普通函数
function fn() {
console.log('111')
}
// 将这个函数放进一个对象里面
let obj = {
fn:function() { // 是这个obj对象的方法
console.log('111')
}
}怎样访问对象里面的属性呢?
创建了一个对象之后,我们怎么访问该怎样访问这个对象里面的属性呢?
声明对象并添加了若干属性后,可以使用
.或[]获得对象中属性对应的值,基本上写法就是 对象名.属性名 或者 对象名[‘属性名’]
如果我们需要为这个属性重新赋值或者添加一个属性,也很简单 对象名.属性名 = 新的属性值
我们平常写代码经常写的
1
2
3
4
5
6
7// 获取DOM元素
let oDiv = document.querySelector('div')
// 给这个元素添加一个字体颜色为红色的样式
oDiv.style.color = 'red'
// style 就是 oDiv 的一个属性,这个属性的值其实也是一个对象,而 color 是 style 的一个属性上面的是不是很熟悉,其实当我们把
div这个标签获取过来并赋给变量oDiv时,我们也相当于创建一个一个对象名为oDiv的对象,只是这个对象里面已经很多自带的属性和方法了,我们用的时候直接使用.调用我们需要的属性,并给它重新赋值就行了怎样访问对象里面的方法呢?
- 基本上和访问属性的写法是一致的,只有一点不一样,我们都知道普通函数的调用方法,
函数名(),最后必须要加上一个小括号,这个函数的调用的方法是不论在哪里都不变的,前面讲到方法的属性值是函数,既然是函数,那我们也应该在属性名后面加上(),即对象名.属性名()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15let person = {
name: '小红',
age: 18,
singing: function() {
console.log('两只老虎,两只老虎,跑的快,跑的快...')
},
run: function () {
console.log('我跑的非常快...')
}
}
// 调用person对象中 singing 方法
person.singing()
// 调用person对象中的 run 方法
person.run()- 概念:对象也是 JS 数据类型的一种,和之前学习的数值类型、字符串类型、布尔类型是一样的。对象数据类型可以被理解成是一组 无序的键值对的 集合,是属性的容器
构造函数
在前面我们学习了对象之后,应该能更好的理解构造函数,因为构造函数其实也是在创建对象
什么是构造函数?
本质上就是一个普通的函数,但是会有两个地方不太一样
命名方面上,构造函数的名称首字母要大写,这其实是一种普遍的命名的约定用来区别普通函数
与
new操作符一起使用:构造函数通过new操作符来调用,用于创建并初始化一个新对象。你们也可以这样理解,当一个函数使用new操作符调用时,它就成为了“构造函数”,即一个用于构造新对象的函数
构造函数的作用:创建对象
在没有构造函数之前,我们使用对象字面量创建单个对象
1
2
3
4
5
6
7
8
9
10// 对象字面量创建单个对象
let person1 = {
name: "Alice",
age: 25,
greet :function() {
console.log(Hello, my name is 11);
}
}
person1.greet(); // 输出: Hello, my name is 11但如果要创建多个结构相似(都有 name, age, greet 属性)的对象,重复写对象字面量会很麻烦,这时构造函数就派上用场了
构造函数就像一个“工厂模具”,可以批量生产具有相同属性和方法结构的对象
怎样使用构造函数呢?
- 第一步:定义构造函数,在其内部使用
this来定义未来的实例对象里面的属性和方法
创建一个函数(首字母要大写)
1
2
3
4
5
6
7// 1. 定义构造函数 (首字母大写)
// 这里相当于创建了一个模板
function Person(name, age) {
// 2. 使用 this 来添加属性
this.name = name;
this.age = age;
}- 第二步:使用
new调用构造函数,使用new操作符来调用Person函数,它会按照构造函数的模板创建一个新的对象并返回它
- 第一步:定义构造函数,在其内部使用
1
2
3
4
5
6
7// 使用 new 创建实例
// 我们在这里调用这个模板
let person1 = new Person('Alice', 25);
let person2 = new Person('Bob', 30);
console.log(person1.name); // 输出: Alice
console.log(person2.age); // 输出: 30我们可以看一个例子:


到这里,我想问大家,现在大家知道上面第二个图片里的变量
person1和person2的值是什么数据类型吗?箭头函数
箭头函数是 ES6 中引入的一种新的函数语法,它提供了一种更简洁的方式来编写函数
基本语法:
- 我们与传统的函数表达式对比来学
1
2
3const add = function(a, b) {
return a + b
}1
2
3
4
5
6
7// 完整写法(多行语句或需要显式返回对象时)
const add = (a, b) => {
return a + b
}
// 简洁写法(单行表达式,隐含 return)
const add = (a, b) => a + b大家观察上面的两种写法有什么不同?
- 仔细观察,赋值等号左边的没有变化,变化在右边,箭头函数删去了 function ,变成了 => ,并且将 => 放到了 小括号 和 大括号 的中间
好啦,到这里我们的前置只知识准备就完成了,这就开始我们第二部分的介绍吧!
this 到底什么?
在正式开始介绍 this 到底是什么之前,这里有两个问题:
this 是不是变量?
- 不是,
this是一个特殊关键字(像function,if,new一样),不是可以声明的变量或对象的属性
- 不是,
this 是对象吗?学到现在,大家应该或多或少都应该见过
this.sayName()这种写法,那按照我们上面对于对象方法的学习,大家觉得 this 是对象吗?- 也不是,this 很多变,它本身不是一个对象,只是它的值实际根据是谁在调用它而变的,只是一般情况下 this 的值(它所指向的东西),总是一个对象
有一个类比,万能电视遥控器(this)
- 万能遥控器(this)本身不是电视,但按下遥控器的按钮(调用 this)的效果,取决于它当前指向并控制的是哪台电视,你可以用同一个遥控器(this),通过改变它的指向(不同的调用方式)来控制不同的电视(不同的对象)
所以 this 到底是什么呢?
一般来说,this 都是放在一个函数中使用的,调用这个函数也就相当于在调用这个 this ,
当我们总结上面的问题和类比例子后,其实可以用一句话总结,就是
this的值不是在我们定义函数时确定的,而是在我们调用这个函数时才被确定的一个贴近生活的比喻:
想象你是一个员工(函数),你有一句话(函数体):“我正在为
this公司工作“如果你在 A 公司 的办公室里说这句话,
this就代表 A 公司如果你在 B 公司 的办公室里说这句话,
this就代表 B 公司你(函数)本身没变,但你所在的环境(调用者)变了,this的含义也就随之改变了
- 说多不如练多,这里有一个小问题,大家可以先试试能不能解答
1 | const person = { |
六个常见环境下的 this 指向问题
全局环境中的 this
- 全局环境就是在
<script></script>里(不在任何函数或对象内部),此时的 this 始终指向的是全局对象,在浏览器中就是 window
1
console.log(this)
- 全局环境就是在
普通函数中的 this
当一个函数被直接调用时,要考虑两种情况
this 在非严格模式下指向全局对象(window)
1
2
3
4
5
6function fun() {
console.log(this)
}
fun()
// 我们实际上是在全局作用域下直接调用函数
// fun() 实际上是window.fun(), 所以this -> window在严格模式下指向 undefined
JS 严格模式:JavaScript 在语法和行为上存在一些模糊的特性,可能导致一些不易察觉的错误,为提高代码的质量和可维护性,JS 引入了严格模式,通过启用一些额外的规则,强制执行更严格的语法和行为,帮助提前发现和修复潜在 bug。
1
2
3
4
5
6
7// 演示严格模式下的情况
function fu() {
"use strict" // 在函数体中写一句 "use strict" ,就可以启用函数的严格模式
console.log(this)
}
fu() // this 指向 undefined对像方法中的 this
- 当函数作为对象的方法被调用时,this 指向该方法所属的对象
1
2
3
4
5
6
7
8let person = {
name: "John",
sayName: function () {
console.log(this.name)
}
}
person.sayName()1
2
3
4
5
6
7
8
9
10
11let name = '卡卡';
let cat = {
name:'有鱼',
eat1:{
name:'年年',
eat2:function(){
console.log(this.name);
}
}
}
cat.eat1.eat2();构造函数中的 this
- 使用 new 关键字(实例化)调用函数时,该函数被当作构造函数,this 会指向新创建的对象实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 构造函数
function Person(name) {
this.name = name
this.sayHello = function () {
console.log("Hello, I'm " + this.name)
}
}
// 创建实例
let john = new Person("John");
john.sayHello() // 输出 "Hello, I'm John",这里 this 指向 john 实例
let john = {
name : name,
sayHello : function () {
console.log("Hello, I'm " + this.name)
}
}1
2
3
4
5
6
7
8
9
10
11// 构造函数
function Person(name) {
this.name = name
this.sayHello = function() {
console.log(`你好,我是 ${this.name}`)
}
}
// 创建实例
const person1 = new Person('张三')
person1.sayHello()事件处理中的 this
- 在 DOM 事件处理函数中,this 通常指向触发事件的元素
1
2
3
4
5
6
7
8<button id="myButton">Click me</button>
<script>
var button = document.getElementById("myButton")
button.onclick = function () {
console.log(this)
}
</script>1
2
3
4
5
6
7
8
9
10
11<button class = "btu">Click</button>
<script>
let oBtu = document.querySelector('.btu')
oBtn.addEventListenter('click',clickBtu)
function clickBtu() {
console.log(this)
}
</script>箭头函数中的 this
箭头函数没有自己的 this,它的 this 直接“捕获”或“继承”**外层作用域(它父级)**的 this 的值
前面介绍过普通函数 的
this是动态的,取决于如何被调用而箭头函数 的
this是词法的,取决于定义时的上下文,且一旦定义就固定不变词法的 = 写代码时候的位置决定的(this 的出生地)
1 | // 普通函数 |
改变 this 指向的方法
由于箭头函数的 this 来自于继承,箭头函数无法使用以下三种方法改变 this 指向
call()方法call()方法附加在函数调用后面使用,可以忽略函数本身的 this 指向,然后将这个函数本身的 this 指向绑定到call()方法的第一个参数上面1
函数.call(thisArg,arg1,arg2,...,argN)
参数解析:
thisArg:就是你想将调用这个方法的函数本身的 this 指向绑定到哪个对象上面arg1-argN:从参数arg1开始,依次是向函数传递参数
注意点:
- 使用
call()方法时,相当于你调用了这个函数,会立刻执行这个函数
- 使用
1
2
3
4
5
6
7
8
9
10function greet() {
console.log(this.animal, "的睡眠时间一般在", this.sleepDuration, "之间");
}
const obj = {
animal: "猫",
sleepDuration: "12 到 16 小时",
};
greet.call(obj); // 猫 的睡眠时间一般在 12 到 16 小时 之间apply()方法和
call()方法很相似,几乎一样1
函数名.apply(thisArg,[arg1,arg2,...,argN])
参数解析:
thisArg:同样为你想将这个函数本身的 this 指向绑定到哪个对象上面[arg1,arg2,...,argN](主要区别点):一个 数组,数组里面的每一项依次是向函数传递的参数,这里可以直接写入一个数组,也可以写一个值为数组的变量
bind()方法和上面两个方法不一样,
bind()方法创建一个新函数并且还有返回值,使用bind()方法后不会立即执行函数,而是返回一个已经改变了 this 指向的函数1
函数名.bind(thisArg, arg1, arg2, ..,argN)
参数解析:
thisArg:作用与前两个一致arg1, arg2, ..,argN: 在调用函数时,插入到传入绑定函数的参数前的参数
返回值:
- 一个改变了 this 指向以后的 function 函数
1 | const module = { |
总结及综合演练
this 指向:
1、一般函数:谁调用函数,this 就指向谁,没有调用者就指向全局对象 Window
2、箭头函数:箭头函数不会创建 this,它的 this 继承自上层作用域中的 this
1 | <button class = 'btu'>Click</button> |
1 | // 构造函数 |
1 | // call()方法 |
代码解析:
关键点:
Product.call(this, name, price)这行代码的作用是:
- 在 Food 的上下文中调用 Product 构造函数
- 第一个参数 this 是新创建的 Food 实例
call()方法将 Product 中的 this 绑定到传入的 Food 实例上- 相当于在 Food 实例上设置 name 和 price 属性
执行过程:
new Food("cheese", 5)创建 Food 实例Food 构造函数中的 this 指向这个新实例
Product.call(this, "cheese", 5)让 Product 构造函数在这个 Food 实例上工作最终创建的 Food 对象包含:
{name: "cheese", price: 5, category: "food"}
1 | const outer = { |