主题
Object 对象与构造函数(Day8 3/4)
本页对应 Day08_JavaScript,共 6 段视频,约 59 分钟。
学习目标
- 能够说明构造函数中
return原始类型数据与对象类型数据时,new实例化结果的差异,并编写代码验证。 - 能够区分同一函数的“调用”和“实例化”用法,并解释其结果不同的原因及如何使二者结果一致。
- 能够说明
Number、String、Boolean原始类型数据的值状态与对象状态,并验证其可自动转换的特性。 - 能够解释原始类型数据被当作对象使用时临时包装、属性添加后随即销毁的机制,并预测相关代码输出。
- 能够指出
this在全局环境、普通函数调用和对象方法调用中的不同取值,并说明其只读特性。 - 能够说明
window作为浏览器环境全局对象的含义,并验证全局变量是window属性的关系。
知识点
广义对象与狭义对象:先把“对象”说清楚
为什么需要: 学 Object 最先容易混的是“对象”这个词。日常说“一切皆对象”,但笔记又强调 Object 数据类型只是对象类型中的一种。如果不先区分广义和狭义,后面看到数组、函数、Object 都会打架。
怎么想: 广义理解是“一切皆对象”,数组、函数都是对象的一种;狭义理解是“Object 数据类型”,它是对象类型中的一种,与 Array、Function 是平级的。也就是说,广义的“对象”是上位概念,狭义的 Object 是具体类型之一。
怎么做: 本节笔记给的两个判断要保留:
js
一切皆对象,数组、函数都是对象的一种。js
Object 数据类型,是对象类型中的一种,与Array、Function 是平级的。易错点: 不要把“数组是对象”理解成“数组就是 Object 类型”。数组是 Array 类型,函数是 Function 类型;它们都属于广义对象,但不等于狭义的 Object 数据类型。
构造函数的返回值:new 出来的到底是谁
为什么需要: 既然对象常由构造函数创建,就要知道构造函数里写 return 会不会改变 new 的结果。转写稿里专门用 Product 做实验:不写 return、return 100、return 数组,结果不同。
怎么想: 规则是人为设计的语法规则:如果构造函数中没有 return,或者 return 的是原始类型数据(number、string、boolean 等),实例化结果就是根据构造函数创建的新对象;如果 return 的是对象类型数据(广义对象,如数组、函数、Object),实例化结果就是 return 的那个对象,不再创建新对象。
怎么做: 可以先写一个空构造函数,new Product() 得到新对象;写 return 100,结果仍是新对象;写 return [],结果就变成那个数组。转写稿还提醒:构造函数里的语句照样执行,return 也能中断构造函数,只是返回值规则要按上面判断。
易错点: 不要把“调用”和“实例化”混了:不带 new 是调用,带 new 是实例化。return 原始类型不会改变实例化结果,但 return 对象类型会直接替换结果。
函数与构造函数:一个东西,看你怎么用
为什么需要: 看到 Array() 和 new Array() 结果相似,容易以为函数和构造函数是两个东西。转写稿用 user、Product 对比说明:它们本质是同一个函数,只是用法不同。
怎么想: 函数名后加小括号调用,它就是函数;用 new 去实例化,它就是构造函数。所有函数都是大写的 Function 的实例;而 Function 的构造函数又是它自己,这是 JS 设计里逻辑自洽但看起来奇怪的一环。
怎么做: 写 function user(){ console.log('hello') },new user() 会执行函数体并得到新对象;user() 也会执行函数体,但没有 return 时得到 undefined。若想让调用和实例化结果一样,可以在构造函数里 return {} 或 return function(){} 这类对象类型数据。
易错点: 不要因为“构造函数”这个名字就以为它不能当普通函数调用;也不要以为调用函数会自动产生新对象。结果一样与否,取决于 return 的数据类型。
原始类型也有对象状态:Number、String、Boolean
为什么需要: 前面说“一切皆对象”,但 number、string、boolean 又被划为原始类型。转写稿用 new Number()、Number()、直接量说明:原始类型有值状态,也能出现对象状态。
怎么想: 直接量和 Number() 创建的是值的状态;new Number() 创建的是对象状态。原始类型在需要当对象用时,会自动包装成对象;需要当值用时,又会转回值。null 和 undefined 没有对象状态,也没有对应的构造函数可用。
怎么做: 转写稿示例:let n01 = 89、let n02 = Number(88)、let n03 = new Number(87)。n01 * n03 会把 n03 当值参与运算;n01.constructor 会把 n01 临时包装成对象再取属性。字符串直接量也能点 .constructor、.length。
易错点: 整数直接量后直接写点可能造成解析歧义,如 12.constructor 可能被当成小数点;可用 (12).constructor 或 12..constructor。null、undefined 不能这样用。
临时包装对象:字符串加属性为什么读不到
为什么需要: 这是转写稿里的“世纪巨坑”:给字符串加属性,语法不报错,但读出来是 undefined。理解它才能真正理解原始类型的对象特性。
怎么想: 原始类型默认是值状态。用点操作时,JS 会临时用 String、Number、Boolean 创建一个包装对象,属性加到或读到这个临时对象上;语句执行完临时对象就销毁。下一次再用点,又会创建新的临时对象,所以上一次加的属性找不到。
怎么做: 转写稿对比:数组 arr.address = '上海' 能读到,因为数组本身就是对象类型;字符串 msg.address = '上海' 后再读 msg.address 得到 undefined。原因就是两次点操作对应两个不同的临时包装对象。
易错点: 不要以为“没报错就等于加上了”。原始类型上加属性只是加在临时对象上,不会留在原始值本身。
this 与 window:对象体系的入口
为什么需要: 转写稿用 this 和 window 说明对象体系:全局 this 是 window,对象方法里的 this 是该对象;浏览器里 window 是全局对象,所有全局变量都是 window 的属性。
怎么想: this 是系统内置的只读变量,不同地方值不同;不能给 this 赋值,也不能拿 this 当变量名。window 在浏览器打开时自动创建,是全局对象;全局变量可直接用,也可写成 window.address,通常省略 window.。系统函数如 parseInt、alert、Number、String、Boolean、Object、Array、Function 也都是 window 的属性。
怎么做: 在全局 console.log(this) 得到 window;在对象方法里 console.log(this) 得到该对象。声明全局变量 address 后,可用 window.address 验证。运行在 Node.js 时全局对象不是 window,因为全局对象由宿主提供。
易错点: 不要把 this 当成普通变量去赋值;也不要把 window 当成所有环境都有的全局对象,它取决于 JS 运行在浏览器还是 Node.js。
构造函数返回值:先看规则,再看现象
为什么需要:实例化 new Product() 到底得到什么,不能靠猜。需要明确 return 对实例化结果的影响,否则会把“构造函数”和“普通函数”混在一起。
怎么想:先区分“调用”和“实例化”。不带 new 是调用;带 new 是实例化,实例化表达式会产生一个结果。构造函数的 return 是否影响这个结果,要看返回的数据类型。
怎么做:先写一个没有 return 的构造函数:
js
function Product() {}
var p = new Product();
console.log(p); // Product 的实例,新对象再写返回原始类型:
js
function Product() {
return 100;
}
var p = new Product();
console.log(p); // 仍是 Product 实例最后写返回对象类型:
js
function Product() {
return [1, 2, 3];
}
var p = new Product();
console.log(p); // [1, 2, 3]规则是:构造函数中没有 return,或 return 的是原始类型数据(number、string、boolean 等),实例化结果就是根据构造函数创建的新对象;如果 return 的是对象类型数据(数组、函数、普通对象等广义对象),实例化结果就是 return 的那个对象。return 仍然能中断构造函数内部语句的执行,这一点和普通函数一样。
易错点:return 100 不会让 new Product() 得到 100;只有返回对象类型才会替换实例化结果。不要把“中断执行”和“改变实例化结果”混为一谈。
构造函数和函数:一个东西,两种用法
为什么需要:Array、Object 等既能调用又能 new,容易让人以为它们是两套东西。其实函数和构造函数是一体两面。
怎么想:看名字后面怎么用。加 new 就是实例化,当构造函数用;直接加小括号就是调用,当普通函数用。
怎么做:
js
function User() {
console.log('hello user');
}
var r1 = new User();
var r2 = User();
console.log(r1); // User 实例,新对象
console.log(r2); // undefined想让调用和实例化结果一样,可以让构造函数返回对象类型:
js
function Product() {
return {};
}
var p1 = Product();
var p2 = new Product();
console.log(p1, p2); // 都是空对象易错点:返回原始类型不能让两者一致;只有返回对象类型,new 的结果才会被替换成该对象。
原始类型的对象特性:值状态与对象状态
为什么需要:JS 说“一切皆对象”,但 number、string、boolean 又被划为原始类型。需要理解它们如何同时满足这两套说法。
怎么想:原始类型有“值状态”,需要当对象用时又能出现“对象状态”。number、string、boolean 都有对象状态;null、undefined 没有。
怎么做:
js
var n01 = 89;
var n02 = Number(88);
var n03 = new Number(87);
console.log(typeof n01); // number
console.log(typeof n02); // number
console.log(typeof n03); // object
console.log(n01 instanceof Number); // false
console.log(n03 instanceof Number); // true
console.log(n01 * n03); // 7743,n03 自动转为值状态
console.log(n01.constructor); // Number,n01 自动包装成对象易错点:整数直接量写 12.constructor 会语法错误,因为点和小数点有歧义;可写 (12).constructor、12..constructor,或先用变量保存。null、undefined 不能这样用。
临时包装对象:字符串加属性为什么读不到
为什么需要:给字符串加属性再读取,结果常是 undefined,这是经典坑。
怎么想:原始类型用点操作时,会临时创建一个包装对象;属性加到临时对象上,语句结束临时对象销毁。下一次读取又重新创建临时对象,所以读不到上一次加的属性。
怎么做:
js
var arr = [];
arr.address = '上海';
console.log(arr.address); // 上海
var msg = 'hello';
msg.address = '上海';
console.log(msg.address); // undefined易错点:数组本身是对象类型,属性可保留;字符串、数字、布尔是原始类型,临时包装对象用完即销毁。
this 与 window:全局对象和只读变量
为什么需要:this 的值在不同位置不同,window 又是浏览器环境的全局对象,必须先认识它们。
怎么想:在全局直接输出 this,浏览器中是 window;普通函数调用时也是 window;对象方法中的 this 是该对象。this 不能赋值,也不能当变量名。
怎么做:
js
console.log(this); // window
function fn() {
console.log(this);
}
fn(); // window
var obj = {
getThis: function () {
console.log(this);
}
};
obj.getThis(); // objwindow 表示浏览器窗口,打开浏览器时自动创建,是浏览器环境下的全局对象。所有全局变量都是 window 的属性,所以 window.address 和 address 等价,通常省略 window.。系统函数如 parseInt、alert、Number、Array、Object、Function 也都是全局变量,即 window 的属性。
易错点:this 的值不是固定不变;window 是浏览器宿主提供的全局对象,换到 Node.js 环境就不是 window。
课堂画面






代码示例
示例一:构造函数的返回值规则
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>构造函数的返回值</title>
</head>
<body>
<script>
// 1. 没有 return:实例化结果是由构造函数创建的新对象
function Product() {}
var p1 = new Product();
console.log('无 return:', p1); // Product {}
// 2. return 原始类型数据(number/string/boolean/null/undefined):不影响实例化结果
function Product2() {
return 100;
}
var p2 = new Product2();
console.log('return 100:', p2); // Product2 {}
// 3. return 对象类型数据(广义对象:数组、函数、{}):实例化结果就是该对象
function Product3() {
return [];
}
var p3 = new Product3();
console.log('return []:', p3); // []
function Product4() {
return {};
}
var p4 = new Product4();
console.log('return {}:', p4); // {}
// 4. return 依然可以中断构造函数体的执行
function Product5() {
console.log('这句会执行');
return 100;
console.log('这句不会执行');
}
new Product5();
</script>
</body>
</html>要点解释与易错点:
new Product()才叫“实例化”,Product()叫“调用”;实例化结果是一个由构造函数创建的新对象。- 构造函数中没有 return 或 return 的是原始类型数据(number、string、boolean 等)时,
new的结果仍是新创建的对象,return 的值被忽略。 - 构造函数中 return 的是对象类型数据(数组、函数、
{}都属于广义对象)时,new的结果就是那个 return 出来的对象,此时不会创建新实例。 - 易错点:
return 100看起来“有返回值”,但它不会替换实例化结果;只有对象类型才会替换。这是 JS 语法规则的人为设定,不需要纠结合理性,记住规则即可。 - 易错点:return 虽然不影响实例化结果(原始类型时),但它中断构造函数执行的功能没有变化,return 后面的代码不会执行。
示例二:构造函数和函数是一体两面
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>构造函数与函数</title>
</head>
<body>
<script>
// 同一个函数,调用它是函数,new 它它就是构造函数
function User() {
console.log('hello user');
}
var res1 = new User(); // 实例化:函数体执行,得到新对象
var res2 = User(); // 调用:函数体执行,得到 return 的值(没有 return 即 undefined)
console.log('res1 =', res1); // User {}
console.log('res2 =', res2); // undefined
// 如何让“调用”和“实例化”结果一样?在函数里 return 一个对象类型数据
function Product() {
return {}; // 对象类型数据
}
var p1 = Product(); // 调用,返回 {}
var p2 = new Product(); // 实例化,return 的是对象,结果也是 {}
console.log('p1 =', p1); // {}
console.log('p2 =', p2); // {}
// 系统内置的 Array 就是这样:调用和实例化结果都是一个数组
console.log(Array(1, 2, 3)); // [1, 2, 3]
console.log(new Array(1, 2, 3)); // [1, 2, 3]
// 所有函数都是 Function 的实例;Function 的构造函数是它自己
console.log(User.constructor === Function); // true
console.log(Function.constructor === Function); // true
</script>
</body>
</html>要点解释与易错点:
User本身就是一个函数,new User()时它扮演构造函数角色,User()时它扮演普通函数角色,东西是同一个东西,看你怎么用。- 调用没有 return 的函数得到
undefined;实例化得到的是新对象,两者结果不同。 - 想让“调用”和“实例化”结果一致,就在函数体内
return一个对象类型数据(如{}、[]、函数);return 原始类型无效。 - 易错点:
new User()时函数体照样会执行,不要以为实例化就不执行函数内部语句。 - 易错点:所有函数都是
Function的实例(User.constructor === Function),而Function.constructor === Function,即Function自己构造了自己,这是 JS 中比较“逆天”的设计,逻辑自洽但不必深究。
示例三:原始类型数据的临时包装(世纪巨坑)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>原始类型临时包装成对象</title>
</head>
<body>
<script>
// 数组是对象类型,可以正常添加、读取属性
var arr = [];
arr.address = '上海';
console.log('arr.address =', arr.address); // 上海
// 字符串是原始类型,添加属性后读取不到 —— 世纪巨坑
var msg = 'hello';
msg.address = '上海';
console.log('msg.address =', msg.address); // undefined
// 原因:每次“点”操作都会临时 new String(msg) 出一个新对象,
// 用完即销毁;两次操作不是同一个临时对象
console.log('msg.length =', msg.length); // 5,临时对象上有 length
console.log('"hello".constructor === String:', 'hello'.constructor === String); // true
// 原始类型的两种状态:值状态 与 对象状态
var n01 = 89; // 直接量:值状态
var n02 = Number(88); // Number 函数:值状态
var n03 = new Number(87); // new Number:对象状态
console.log(n01, n02, n03); // 89 88 [Number: 87]
console.log(n01 + n03); // 176,n03 自动转为值状态
console.log(n03 * n01); // 7743,n03 自动转为值状态
console.log(n01.constructor); // [Function: Number],n01 自动包装成对象
console.log('constructor' in n03); // true,n03 本身就是对象状态
console.log('constructor' in n01); // false,in 不触发包装
// 数字直接量后接点会与小数点产生歧义,这是纯语法错误
// console.log(12.constructor); // 报错:SyntaxError
console.log((12).constructor); // 正确:加括号
console.log(12..constructor); // 正确:两个点,一个小数点一个属性点
console.log(12.5.constructor); // 正确:小数没有歧义
</script>
</body>
</html>要点解释与易错点:
- 原始类型(number、string、boolean)既有值状态,也有对象状态;
new Number()得到的是对象状态,直接量和Number()得到的是值状态。 - 当把原始类型当对象用(点属性、点方法)时,JS 会临时包装成对应对象(如
new String(msg)),属性添加/读取都发生在这个临时对象上,语句执行完临时对象即销毁。 - 因此
msg.address = '上海'加到了一个转瞬即逝的临时对象上,下一行msg.address又临时创建了另一个新对象,自然读不到,得到undefined。 - 数组没有这个坑,因为数组本身就是对象类型,没有“值状态”需要转换,属性直接加在数组对象自身上。
- 易错点:
12.constructor报语法错误,浏览器分不清点是小数点还是属性访问点;解决方式是(12).constructor或12..constructor,变量形式(如n01.constructor)不会产生歧义。 - 易错点:
in运算符不会触发临时包装,所以'constructor' in n01为false,而'constructor' in n03为true(n03 本身就是对象)。
常见错误
- 现象:
new F()里写了return 100,结果仍是实例。→ 原因:构造函数返回原始类型会被忽略。→ 修法:要替换实例结果,必须return对象类型(对象/数组/函数)。 - 现象:
F()得到undefined,new F()得到实例,误以为二者应一致。→ 原因:调用取返回值,实例化按new规则取结果。→ 修法:先明确是否使用new;若需一致,在构造函数中返回对象。 - 现象:
let s='abc'; s.x=1; console.log(s.x)得到undefined。→ 原因:赋值加在临时包装对象上,语句结束即销毁,读取时又新建临时对象。→ 修法:把属性放到普通对象/数组等真实对象上,不要依赖原始值持久保存属性。 - 现象:
12.constructor报语法错误,而12.2.constructor正常。→ 原因:整数后的点被解析为小数点,产生歧义。→ 修法:写成(12).constructor、12..constructor,或先存入变量。 - 现象:对
null/undefined点属性或取constructor报错。→ 原因:只有number/string/boolean有临时对象包装,null/undefined没有对象状态。→ 修法:先判空,再访问属性。 - 现象:对象方法里以为
this一定是window。→ 原因:this随调用上下文变化。→ 修法:按调用方式判断;不要给this赋值,也不要把它当普通变量改名。
练习
- 写构造函数
Product,使Product()与new Product()返回同类结果,并说明return应写什么。 - 判断输出并解释:
function User(){ return 100 } const a=new User(); const b=User(); console.log(a,b)。 - 排查并改造:
let s='abc'; s.x=1; console.log(s.x)为什么是undefined?给出能保存x的方案。 - 修正
console.log(12.constructor)的语法错误,写出两种可行写法。 - 判断两次输出并说明原因:
const obj={m(){console.log(this===obj)}}; obj.m(); const f=obj.m; f();。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 构造函数的返回值问题 | 07:27 |
| 2 | 构造函数和函数 | 14:26 |
| 3 | 原始类型数据的对象特性 | 15:37 |
| 4 | 原始类型数据临时包装成对象 | 08:29 |
| 5 | 认识this | 05:38 |
| 6 | window | 07:56 |
小结
new表达式的结果优先看构造函数return:原始值忽略,对象值替换实例。- 函数与构造函数是同一函数值的两种使用方式,区别在调用方式,不在定义。
- 原始类型的“对象特性”是临时包装,属性不可持久;
null/undefined不适用。 - 整数直接量点属性易触发语法歧义,括号、双点或变量可规避。
this是只读且随调用上下文变化,不能赋值或作为变量名。- 浏览器中
window是全局对象,全局变量是其属性,window.通常可省略;宿主变化时全局对象也变化。 - 调试对象/属性问题先区分“值状态/对象状态”和“是否新建了临时对象”。