主题
构造函数与this(Day8 4/4)
本页对应 Day08_JavaScript,共 6 段视频,约 50 分钟。
学习目标
- 能够编写在构造函数中使用
this为实例添加属性和方法的代码,并说明每次new时this指向当前创建的实例。 - 能够根据函数调用形式(如
obj.method()、method()、回调函数调用等)判断this的指向,并依据“谁调用指向谁”解释判断过程。 - 能够独立分析涉及
this、作用域和对象属性访问的题目,写出输出结果并逐步说明理由。 - 能够区分
this的动态绑定规则与词法作用域规则,说明分析this时应关注调用位置而非函数声明位置。 - 能够说明全局变量是
window的属性,并判断访问不存在的变量、不存在的对象属性时分别报错还是得到undefined。
知识点
转写稿里的“ra/race/瑞子/认死”都是 this,“win度/温州”是 window。本节无图片;转写稿中的代码整理如下。先记住总规则:this 的值是动态的,不看函数声明在哪,只看调用时谁在调用。
先明确:this 不是“指向”,就是当前值
为什么需要:很多同学把“指向”理解成复杂机制,其实初学阶段先把它当成一个变量:它当前的值是谁,this.xxx 就是谁的属性。
怎么想:this 在全局、构造函数、普通函数/方法里取值不同,要分三种情况。
怎么做:全局使用 this,值是 window;构造函数内部,值是 new 出来的实例;函数/方法中,值是调用它的对象。
易错点:不要把 this 和作用域混在一起。作用域看函数创建位置,this 看调用位置,方向相反。
构造函数内部:this 是 new 出来的实例
为什么需要:构造函数要给“将来创建的对象”添加属性,不能写死某个变量名,所以用 this 代表当前实例。
怎么想:new User(...) 执行一次,构造函数体执行一次;这一次的 this 就是这次 new 创建的对象。
怎么做:看下面代码,u1 创建时 this 是 u1,u2 创建时 this 是 u2,互不冲突。
易错点:不写 new,构造函数不会按预期创建实例;构造函数里的局部变量也不会自动变成实例属性,必须写 this.a。
js
function User(name, age, address) {
this.name = name;
this.age = age;
this.address = address;
this.addToCart = function (product) {
console.log(this.name + '将' + product + '加入购物车');
};
}
var u1 = new User('高小乐', 19, '上海');
var u2 = new User('曹操', 89, '北京');
console.log(u1); // this 指向 u1
console.log(u2); // this 指向 u2函数/方法中:谁调用指向谁
为什么需要:对象方法里经常要访问“当前对象”的属性,this 决定访问的是哪个对象。
怎么想:看调用语句里函数名前面有没有“点什么”。有点,点前对象就是 this;没有点,默认是 window 调用。
怎么做:user.getInfo() 中 this 是 user;user.friend.say() 中 this 是 friend;直接 func() 中 this 是 window。
易错点:把方法赋值给变量后再调用,调用形式变了,this 也会变。
js
var user = {
name: '高小乐',
getInfo: function () {
console.log(this.name);
},
friend: {
name: '曹操',
say: function () {
console.log(this.name);
}
}
};
user.getInfo(); // 高小乐,this 是 user
user.friend.say(); // 曹操,this 是 friend
function func() {
console.log(this.name);
}
func(); // this 是 window
var drink = user.getInfo;
drink(); // this 是 window,不是 user调试推导:同一个函数,换调用方式 this 就变
为什么需要:面试题常把函数传来传去,必须抓住“最终调用点”。
怎么想:不要看函数原来写在哪个对象里,只看最后执行时 fn()、obj.fn()、obj.a.fn() 长什么样。
怎么做:fun() 无点,this 是 window;a.c() 有点,this 是 a;b.say(a.c) 里回调执行的是 fn(),无点,仍是 window。
易错点:看到函数定义在 a 里,就以为 this 永远是 a,这是错的。
js
var name = '222';
var a = {
name: '111',
c: function () { console.log(this.name); }
};
var fun = a.c;
fun(); // 222:无点,window 调用
a.c(); // 111:a 调用
var b = {
name: '333',
say: function (fn) { fn(); }
};
b.say(a.c); // 222:fn() 无点,window 调用
b.c = a.c;
b.c(); // 333:b 调用全局变量是 window 的属性
为什么需要:很多题里 this.foo = 234 会改到全局变量,不理解这一点就会算错。
怎么想:全局变量本质上是 window 的属性;this 是 window 时,this.foo 就是 window.foo,也就是全局 foo。
怎么做:下面案例中 print() 无点调用,this 是 window,this.foo = 234 把全局 foo 改成 234。
易错点:使用不存在的变量会报错;使用对象不存在的属性、数组不存在的元素,得到 undefined。window.address 不报错,直接 address 会报错。
js
var foo = 123;
function print() {
this.foo = 234; // this 是 window,改的是全局 foo
console.log(foo); // 234
}
print();
console.log(foo); // 234
console.log(window.address); // undefined
// console.log(address); // ReferenceError构造函数里的变量作用域与普通函数一样
为什么需要:构造函数里既有局部变量又有实例属性,容易把 a 和 this.a 混为一谈。
怎么想:构造函数本质还是函数,局部变量按作用域规则找;this.a 才是在读新实例的属性。
怎么做:var a = 0 是局部变量;新实例上没有 a 属性,所以 this.a 得到 undefined。
易错点:不要以为构造函数里的 a 会自动挂到新对象上,必须写 this.a。
js
function Test() {
var a = 0;
console.log(a); // 0,本作用域局部变量
console.log(this.a); // undefined,新实例没有 a 属性
console.log(a); // 0
}
new Test();最后再压缩成一句:构造函数内看 new 出来的实例;普通函数/方法看调用点,点前是谁就是谁,没有点就是 window;this 是动态的,别拿声明位置判断。
先明确:this 到底在问什么
这一节讨论的不是“this 怎么写”,而是“this 的值是谁”。在代码里,this 是一个系统内置的变量,它当然有值;所谓“this 的指向”,本质上就是问 this 当前取值是什么。难点在于:this 的值不是固定不变的,它会随着使用位置和调用方式变化。
初学阶段先记住三条规则:
- 在函数外边、全局作用域中使用,
this是window。 - 在构造函数内部使用,
this是本次new所创建出来的实例对象。 - 在函数或方法中使用,
this是“谁调用这个函数/方法,就是谁”,常概括为“谁调用指向谁”。
其中第三条最难,因为调用点有时不明显。
构造函数中的 this:本次 new 出来的对象
为什么需要它?因为构造函数要给“将来创建出来的对象”添加属性。若没有 this,构造函数内部无法表示“当前正在创建的那个实例”。
怎么想?看 new 的时刻。构造函数体不是写了就执行,而是 new 时才执行;每 new 一次,就执行一次,this 就是那一次创建出来的对象。
怎么做?看下面的例子:
js
function User(name, age, address) {
this.name = name;
this.age = age;
this.address = address;
this.addToCart = function (product) {
console.log(this.name + "将" + product + "加入购物车");
};
}
var u1 = new User("高小乐", 19, "上海");
console.log(u1); // this.name 等属性都加到了 u1 上这里 this.name = name 就是把形参 name 加到本次创建的对象上。u1 和构造函数里的 this 指向同一个对象。
易错点:如果再 new 一次:
js
var u2 = new User("曹操", 89, "北京");不要纠结“this 到底是 u1 还是 u2”。创建 u1 时,this 是 u1;创建 u2 时,this 是 u2。两次实例化各执行一次构造函数,互不冲突。
函数/方法中的 this:盯住调用点
为什么难?因为函数声明的位置不等于调用位置。this 看的是“最终谁调用了它”,不是“它写在哪里”。
怎么想?看调用语句里函数名前面有没有“某某点”。有点,点前面的对象就是 this;没有点,就是 window 调用。
js
var user = {
name: "高小乐",
age: 189,
getInfo: function () {
console.log("getInfo");
console.log(this);
console.log(this.name);
},
friend: {
name: "曹操",
say: function () {
console.log("say");
console.log(this.name);
}
}
};
user.getInfo(); // this 是 user,输出高小乐
user.friend.say(); // this 是 user.friend,输出曹操user.friend.say() 中,say 前面是 user.friend,所以 this 是 friend 这个对象。可以用“外婆、妈妈和你”的关系帮助理解:谁直接调用了这个方法,就看谁。
再看全局函数:
js
var name = "龙傲天";
function func() {
console.log(this);
console.log(this.name);
}
func(); // 前面没有点,等价于 window.func(),this 是 window易错点:全局变量本质上是 window 的属性,所以 this.name 能取到全局 name。
this 是动态的:同一个函数,调用方式不同结果不同
这是本节最重要的结论。不要把 this 当成词法作用域那样“看函数声明在哪里”,而要看调用语句。
js
var drink = user.getInfo;
drink(); // 前面没有点,this 是 windowdrink 和 user.getInfo 的值是同一个函数,但 user.getInfo() 由 user 调用,drink() 由 window 调用,所以 this 不同。再如:
js
user.friend.eat = func;
user.friend.eat(); // this 是 user.friend虽然 func 声明在外面,但最终是 user.friend.eat() 调用,点前是 user.friend,所以 this 就是它。
面试题拆解:只找调用点
js
var name = "222";
var a = {
name: "111",
say: function () {
console.log(this.name);
}
};
var fun = a.say;
fun(); // 222:fun() 前面没有点,this 是 window
a.say(); // 111:a.say() 由 a 调用,this 是 a高阶函数里也一样:
js
var b = {
name: "333",
say: function (fn) {
fn();
}
};
b.say(a.say); // 222:fn() 调用时前面没有点,this 是 window
b.say = a.say;
b.say(); // 333:b.say() 由 b 调用,this 是 b易错点:不要因为函数 originally 写在 a 里,就认为 this 永远是 a。this 看调用点。
结合作用域与 undefined 的题目
js
var foo = 123;
function print() {
this.foo = 234;
console.log(foo);
}
print(); // 234
console.log(foo); // 234print() 无点调用,this 是 window;this.foo = 234 改的是全局变量 foo,因为全局变量就是 window 的属性。
再看:
js
function Test() {
var a = 0;
console.log(a); // 0
console.log(this.a); // undefined
console.log(a); // 0
}
new Test();构造函数里 var a 是局部变量,不是实例属性;this.a 读取新对象上不存在的属性,得到 undefined。注意:使用不存在的变量会报错;读取对象不存在的属性、数组不存在的元素,得到 undefined;而 window.address 这种写法是在读 window 的不存在属性,也得到 undefined。
课堂画面






代码示例
示例一:在构造函数中使用 this
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>构造函数中使用 this</title>
</head>
<body>
<script>
function User(name, age, address) {
// 这里的 this 就是 new 出来的那个对象
this.name = name;
this.age = age;
this.address = address;
this.addShopcart = function (product) {
console.log(this.name + ' 把 ' + product + ' 加入了购物车');
};
console.log('构造函数内部的 this:', this);
}
var u1 = new User('高小乐', 19, '上海');
var u2 = new User('曹操', 89, '北京');
console.log('外部的 u1:', u1); // 与构造函数内部打印的 this 是同一个对象
console.log('外部的 u2:', u2);
u1.addShopcart('手机');
u2.addShopcart('电脑');
</script>
</body>
</html>要点解释
this.name = name:构造函数里的this指向「本次new所创建的对象」,所以属性全部挂到新对象上。- 只有在
new User(...)执行时,构造函数体才会运行;不写new,里面的代码一行都不执行。 - 同一个构造函数被实例化多次时
this不冲突:创建u1时this是u1,创建u2时this是u2。
易错点
- 构造函数内部
this不是window,而是将来要创建的实例;在全局作用域下this才是window。 - 想给实例加属性/方法,必须写成
this.xxx = ...;写成var a = 0只是局部变量,不会成为实例属性。
示例二:在函数(方法)中使用 this —— 谁调用指向谁
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>在函数中使用 this</title>
</head>
<body>
<script>
var user = {
name: '高小乐',
age: 189,
getInfo: function () {
console.log('getInfo');
console.log(this);
console.log(this.name);
},
friends: {
name: '曹操',
say: function () {
console.log('say');
console.log(this);
console.log(this.name);
}
}
};
// 1. 点前面是 user
user.getInfo();
// 2. 点前面是 user.friends(不是 user)
user.friends.say();
// 3. 全局变量 name,本质是 window.name
var name = '龙傲天';
function func() {
console.log('func');
console.log(this);
console.log(this.name);
}
// 4. func 前面没有点,等价于 window.func()
func();
// 5. 把方法的值赋给变量,调用时前面没有点
var drink = user.getInfo;
drink(); // this 是 window,不是 user
// 6. 同一个函数,赋给 user.friends.eat 后由 friends 调用
user.friends.eat = func;
user.friends.eat(); // this 是 user.friends(曹操)
</script>
</body>
</html>要点解释
- 判断规则只有一条:看调用语句里点前面是哪个对象,
this就是谁。 user.friends.say()只看最后一个点前面的对象,即user.friends,所以this.name是「曹操」。func()前面没有点,实际是window.func(),所以this是window,this.name是全局的「龙傲天」。drink = user.getInfo只是把函数的值取出来,drink()调用时前面没有点,this变成window。
易错点
this是动态的:不要看函数在哪里声明,要看函数在哪里被调用(与作用域规则正好相反)。user.getInfo与drink指向同一个函数,但调用形式不同,this就不同。- 全局变量就是
window的属性,所以var name = '龙傲天'后window.name也是它。
示例三:this 面试题综合
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>this 题目</title>
</head>
<body>
<script>
var name = '222';
var a = {
name: '111',
say: function () {
console.log(this.name);
}
};
var fun = a.say;
fun(); // 222:fun() 前面没点,this 是 window
a.say(); // 111:a.say(),this 是 a
var b = {
name: '333',
say: function (fn) {
fn(); // 这里调用的是形参 fn,前面没有点
}
};
b.say(a.say); // 222:fn 被 window 调用
b.say = a.say;
b.say(); // 333:b.say(),this 是 b
</script>
</body>
</html>要点解释
var fun = a.say; fun():函数内容没变,但调用时前面没有点,this是window,输出全局的222。b.say(a.say):实参a.say赋给形参fn,等价于fn = a.say;函数体里是fn(),前面没有点,仍然是window调用,输出222。b.say = a.say; b.say():此时是b在调用,this是b,输出333。
易错点
- 形参
fn拿到函数值后,它只是普通变量,不是任何对象的属性,所以调用时this一定是window。 - 只要调用语句里点前面没有对象,就一律当成
window.xxx()处理。
常见错误
- 现象:把构造函数当普通函数直接调用,
this输出成window→ 原因:没有用new,调用点前无对象 → 修法:构造函数统一用new调用,并检查调用形式。 - 现象:看到函数写在某个对象里,就认定
this一定是该对象 → 原因:按声明位置判断this→ 修法:只看最终调用语句,重点看调用点前的“点”。 - 现象:
var f = obj.method; f();后仍认为this是obj→ 原因:函数被赋值给变量后,调用点已变成无点调用 → 修法:分析赋值后的调用点,必要时用obj.method()或显式绑定。 - 现象:回调函数里
this丢失,输出了全局值 → 原因:回调以普通函数形式被调用 → 修法:保留调用点、使用绑定,或改用能保留this的写法。 - 现象:构造函数里写
var a = 9;却以为实例上有this.a→ 原因:局部变量未挂到实例上 → 修法:需要实例属性时显式写this.a = ...。 - 现象:访问不存在的变量直接报错,访问不存在的属性却得到
undefined,误以为规则矛盾 → 原因:变量用法与属性用法不同 → 修法:区分“变量是否存在”和“对象/数组元素是否存在”,按使用方式判断。 - 现象:多次
new后纠结this到底指哪个实例 → 原因:把多次实例化混成一次执行 → 修法:按每次new的调用分别分析,本次new对应本次实例。
练习
- 分析输出:
js
var name = 'global';
var obj = {
name: 'obj',
fn: function () {
console.log(this.name);
}
};
var f = obj.fn;
f();
obj.fn();- 分析三处输出:
js
var a = 0;
function Test() {
var a = 9;
console.log(a);
console.log(this.a);
console.log(a);
}
new Test();- 分析输出:
js
var name = 'global';
var A = {
name: 'A',
say: function () {
console.log(this.name);
}
};
var B = {
name: 'B',
say: function (fn) {
fn();
}
};
B.say(A.say);- 分析输出:
js
var foo = 123;
function print() {
console.log(foo);
this.foo = 234;
console.log(foo);
}
print();
console.log(foo);- 分析输出:
js
var user = {
name: '高小乐',
getInfo: function () {
console.log(this.name);
}
};
var friend = {
name: '曹操',
say: user.getInfo
};
friend.say();原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 在构造函数中使用this | 08:46 |
| 2 | 在函数中使用this | 16:04 |
| 3 | this题目01 | 10:54 |
| 4 | this题目02 | 03:48 |
| 5 | this的题目03 | 06:52 |
| 6 | 全局变量是window属性 | 03:36 |
小结
- 判断
this先找最终调用点,再看调用点前有没有对象。 - 无点调用按
window处理;有点调用看“点”前是谁。 - 构造函数内
this对应本次new创建的实例,多次实例化分别判断。 - 函数声明位置不决定
this,赋值、回调、转存都会改变调用点。 - 构造函数内局部变量不等于实例属性,实例属性必须显式挂到
this。 - 全局变量与
window属性关系密切,但“变量用法”和“属性用法”对不存在情况的处理不同。 - 综合题要同时检查
this、作用域、undefined与报错规则,不能只看单一知识点。