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

例子1-准备月饼模具阶段 
例子2-做月饼阶段 到这里,我想问大家,现在大家知道上面第二个图片里的变量
person1和person2的值是什么数据类型吗? -
-
箭头函数
-
箭头函数是 ES6 中引入的一种新的函数语法,它提供了一种更简洁的方式来编写函数
-
基本语法:
- 我们与传统的函数表达式对比来学
const add = function(a, b) { return a + b }// 完整写法(多行语句或需要显式返回对象时) 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的含义也就随之改变了
-
-
- 说多不如练多,这里有一个小问题,大家可以先试试能不能解答
const person = {
name: '小明',
sayHi: function() {
console.log('你好,我是' + this.name)
}
}
person.sayHi()
六个常见环境下的 this 指向问题
-
全局环境中的 this
- 全局环境就是在
<script></script>里(不在任何函数或对象内部),此时的 this 始终指向的是全局对象,在浏览器中就是 window
console.log(this) - 全局环境就是在
-
普通函数中的 this
当一个函数被直接调用时,要考虑两种情况
this 在非严格模式下指向全局对象(window)
function fun() { console.log(this) } fun() // 我们实际上是在全局作用域下直接调用函数 // fun() 实际上是window.fun(), 所以this -> window在严格模式下指向 undefined
JS 严格模式:JavaScript 在语法和行为上存在一些模糊的特性,可能导致一些不易察觉的错误,为提高代码的质量和可维护性,JS 引入了严格模式,通过启用一些额外的规则,强制执行更严格的语法和行为,帮助提前发现和修复潜在 bug。
// 演示严格模式下的情况 function fu() { "use strict" // 在函数体中写一句 "use strict" ,就可以启用函数的严格模式 console.log(this) } fu() // this 指向 undefined -
对像方法中的 this
- 当函数作为对象的方法被调用时,this 指向该方法所属的对象
let person = { name: "John", sayName: function () { console.log(this.name) } } person.sayName()let name = '卡卡'; let cat = { name:'有鱼', eat1:{ name:'年年', eat2:function(){ console.log(this.name); } } } cat.eat1.eat2(); -
构造函数中的 this
- 使用 new 关键字(实例化)调用函数时,该函数被当作构造函数,this 会指向新创建的对象实例
// 构造函数 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) } }// 构造函数 function Person(name) { this.name = name this.sayHello = function() { console.log(`你好,我是 ${this.name}`) } } // 创建实例 const person1 = new Person('张三') person1.sayHello() -
事件处理中的 this
- 在 DOM 事件处理函数中,this 通常指向触发事件的元素
<button id="myButton">Click me</button> <script> var button = document.getElementById("myButton") button.onclick = function () { console.log(this) } </script><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 的出生地)
-
// 普通函数
function outerFunction() {
this.name = "Outer"
var innerFunction = function () {
console.log(this.name)
}
innerFunction()
}
// 箭头函数
function outerFunctionWithArrow() {
this.name = "外层作用域的this"
var innerFunction = () => {
console.log(this.name)
}
innerFunction()
}
// 一种情况
new outerFunction()
new outerFunctionWithArrow()
// 另一种调用情况
outerFunction()
outerFunctionWithArrow()
改变 this 指向的方法
由于箭头函数的 this 来自于继承,箭头函数无法使用以下三种方法改变 this 指向
-
call()方法call()方法附加在函数调用后面使用,可以忽略函数本身的 this 指向,然后将这个函数本身的 this 指向绑定到call()方法的第一个参数上面函数.call(thisArg,arg1,arg2,...,argN)-
参数解析:
-
thisArg:就是你想将调用这个方法的函数本身的 this 指向绑定到哪个对象上面 -
arg1-argN:从参数arg1开始,依次是向函数传递参数
-
-
注意点:
- 使用
call()方法时,相当于你调用了这个函数,会立刻执行这个函数
- 使用
function greet() { console.log(this.animal, "的睡眠时间一般在", this.sleepDuration, "之间"); } const obj = { animal: "猫", sleepDuration: "12 到 16 小时", }; greet.call(obj); // 猫 的睡眠时间一般在 12 到 16 小时 之间 -
-
apply()方法和
call()方法很相似,几乎一样函数名.apply(thisArg,[arg1,arg2,...,argN])-
参数解析:
-
thisArg:同样为你想将这个函数本身的 this 指向绑定到哪个对象上面 -
[arg1,arg2,...,argN](主要区别点):一个 数组,数组里面的每一项依次是向函数传递的参数,这里可以直接写入一个数组,也可以写一个值为数组的变量
-
-
-
bind()方法和上面两个方法不一样,
bind()方法创建一个新函数并且还有返回值,使用bind()方法后不会立即执行函数,而是返回一个已经改变了 this 指向的函数函数名.bind(thisArg, arg1, arg2, ..,argN)-
参数解析:
-
thisArg:作用与前两个一致 -
arg1, arg2, ..,argN: 在调用函数时,插入到传入绑定函数的参数前的参数
-
-
返回值:
- 一个改变了 this 指向以后的 function 函数
-
const module = {
x: 42,
getX: function () {
return this.x;
},
};
console.log(module.getX())
const unboundGetX = module.getX;//undefined 函数未被调用,存储的是一个值
console.log(unboundGetX());
const boundGetX = unboundGetX.bind(module);
console.log(boundGetX());
总结及综合演练
this 指向:
1、一般函数:谁调用函数,this 就指向谁,没有调用者就指向全局对象 Window
2、箭头函数:箭头函数不会创建 this,它的 this 继承自上层作用域中的 this
<button class = 'btu'>Click</button>
<script>
console.log(this)
// 一个普通函数
function fn() {
console.log(this)
}
// 创建一个对象
let obj = {
fn: fn
}
// 直接调用
fn()
// 对象方法调用
obj.fn()
// 事件处理中调用
let oBtu = document.querySelector('.btu')
oBtu.addEventLister('click',fn)
</script>
// 构造函数
function Fn(age) {
this.name = 111
this.age = age,
this.currentAge = function() {
console.log(this.age)
}
}
let Fn1 = new Fn(16)
Fn1.currentAge()
// call()方法
function Product(name, price) {
this.name = name;
this.price = price;
}
function Food(name, price) {
Product.call(this, name, price);
this.category = "food";
}
console.log(new Food("cheese", 5).name);
-
代码解析:
-
关键点:
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"}
-
-
const outer = {
name: "Outer Object",
innerFunction: function () {
const inner = {
name: "Inner Object",
nestedFunction: function () {
console.log(this.name)
}
}
inner.nestedFunction()
}
}
outer.innerFunction();
If you enjoyed this, leave a comment~