主题
JavaScript 内置对象(内置构造函数)与Boolea(Day10 1/2)
本页对应 Day10_JavaScript,共 7 段视频,约 1 小时 33 分。
学习目标
- 能够使用
__proto__和构造函数.prototype获取对象原型,准确说明函数对象__proto__与prototype的指向差异,并画出给定对象的原型链。 - 能够分析包含构造函数、实例化、重写
prototype的代码,准确写出输出结果并解释属性沿原型链的查找过程。 - 能够区分值类型与引用类型在栈/堆中的存储、赋值和可变性差异,并据此预测相关代码的输出结果。
- 能够依据地址判断引用类型是否相等,编写示例说明对象/数组字面量不相等、同一引用相等,并解释变量对保持数据一致性的作用。
- 能够分析函数传参中基本类型与引用类型的不同影响,准确写出给定题目中的输出值或报错位置。
- 能够说明 Boolean、Number 的直接量、函数调用和
new创建方式的区别,正确使用toFixed、toString(radix)、Number.MAX_VALUE、Number.MIN_VALUE并判断返回类型。
知识点
文档入口与学习范围
为什么需要:MDN 是最权威的 JavaScript 文档,但直接读会“越看越懵”,因为里面混着原型链、实例属性、构造函数属性。
怎么想:先学 ES6 之前就存在、现在仍在用的内置构造函数;看到 Number.prototype.xxx 要知道它是“实例原型上的方法”,看到 Number.xxx 要知道它是“构造函数自身的属性”。
怎么做:入口是 MDN 标准内置对象。本节先看 Boolean、Number。
易错点:不要把 Number.MAX_VALUE 当成实例属性;不要一上来啃完整文档。
原型与原型链:先把关系说清
为什么需要:内置对象的方法大多放在原型上,理解原型才能看懂文档,也能理解“节省内存”。
怎么想:对象有 __proto__ 指向原型;构造函数有 prototype 指向实例原型;构造函数本身也是函数对象。
怎么做:例如 arr.__proto__ === Array.prototype,Array.prototype.constructor === Array;实例链是 arr -> Array.prototype -> Object.prototype -> null。特殊点:Object.prototype 是顶层原型,其 __proto__ 为 null;Object.create(null) 可创建无原型对象;所有函数的构造函数是 Function,Function.__proto__ === Function.prototype。
易错点:函数的 __proto__ 是“它作为对象”的原型,函数的 prototype 是“它创建的实例”的原型;hasOwnProperty 只判断自身,in 会查原型链。
原型链题目:分两条链看
为什么需要:判断某个方法到底在不在对象上。
怎么想:先看实例的构造函数,列实例链;再看函数本身作为对象的链,两条链不要混。
怎么做:
js
function F() {}
Object.prototype.a = 'a';
Function.prototype.b = 'b';
const f = new F();
console.log(f.a, f.b); // a undefined
console.log(F.a, F.b); // a b推导:f -> F.prototype -> Object.prototype -> null,所以有 a 无 b;F -> Function.prototype -> Object.prototype -> null,所以 a、b 都有。
易错点:不要被调试工具显示骗;Function.prototype 不在实例 f 的链上。
值类型与引用类型:内存与传值
为什么需要:解释赋值后“互不影响”还是“互相影响”。
怎么想:值类型整体存栈;引用类型变量存地址,对象在堆。
怎么做:
js
let a = 100, b = a; b = 200; console.log(a); // 100
let o1 = { h: 100 }, o2 = o1; o2.h = 200; console.log(o1.h); // 200
o2 = { h: 400 }; console.log(o1.h); // 200推导:o2 = o1 复制的是地址,改属性是同一对象;o2 = { h: 400 } 是重新赋新对象,断开联系。
易错点:直接给变量重新赋值和修改对象属性不同;数组直接量每写一次就是新对象。
引用类型判等:看地址不看长相
为什么需要:比较对象、数组时容易想当然。
怎么想:值类型比值;引用类型比地址。
怎么做:'hello' === 'hello' 为 true;{name:'小乐'} === {name:'小乐'} 为 false;const ar1=[]; const ar2=ar1; ar1===ar2 为 true;[] === [] 为 false。
易错点:长得一样不代表相等;用变量保存对象可保证数据一致性。
引用传值题目:形参是局部变量
为什么需要:函数传对象后,修改是否影响外部。
怎么想:形参是局部变量;传对象时复制的是地址。
怎么做:
js
let name = 50;
function f1(name) { name = 60; console.log(name); } // 60
f1(name); console.log(name); // 50
function f2(ar) { for (let i = 0; i < ar.length; i++) ar[i] += 2; }
let arr = [1, 2]; f2(arr); console.log(arr); // [3, 4]易错点:函数内 pp = {name:'AA'} 只改局部变量;pp.name='LS' 改同一对象;函数外访问局部形参会报错。
Boolean 内置构造函数
为什么需要:理解包装对象和值状态、对象状态。
怎么想:直接量、函数调用得到值状态;new Boolean 得到对象状态。
怎么做:true、Boolean(0)、new Boolean(false)。
易错点:Boolean 实例没有常用属性和方法;不要用 new Boolean(false) 做条件判断,它是对象,判断时不是简单 false。
Number 内置构造函数
为什么需要:数字格式化与进制转换。
怎么想:实例方法用 num.toFixed();构造函数属性用 Number.MAX_VALUE。
怎么做:
js
let n = 100.907;
console.log(n.toFixed(2)); // "100.91" 字符串,四舍五入
console.log(n.toFixed()); // "101"
console.log((100).toString(2)); // "1100100"
console.log(Number.MAX_VALUE, Number.MIN_VALUE);易错点:toFixed 返回字符串且不改变原变量;toString(radix) 的进制范围是 2–36,超出报 RangeError;MAX_VALUE/MIN_VALUE 是构造函数属性,MIN_VALUE 是最小正数,不是最小数字。
从“内置对象”看 Boolean 的定位
为什么需要:学到内置对象时,容易把“构造函数本身”和“它创建出来的实例”混在一起。转写里提到,标准内置对象文档会写 Number、Number.prototype 这类内容,如果没有原型和原型链的基础,打开文档会看不懂。Boolean 也一样,先要知道它既是内置构造函数,也是创建布尔值的一种方式,才能理解后面的三种写法。
怎么想:把 Boolean 放回“构造函数体系”里看。转写说得很直接:布尔实例就俩,一个是 true,一个是 false;它俩没什么属性和方法。所以 Boolean 这一节的重点不是背一堆 API,而是分清“直接量”“函数方式”“构造函数方式”三种创建方式,以及它们得到的是值状态还是对象状态。
怎么做:先保留本节笔记里的三种写法:
js
// 1. 直接量方式
true;
false;
// 2. Boolean 函数方式
Boolean(true);
Boolean(false);
// 3. Boolan 构造函数方式 默认对象状态
new Boolean(true);
new Boolean(false);易错点:代码注释里的 Boolan 是笔记中的拼写,不要跟着写错;实际构造函数名是 Boolean。另外,不要一看到 Boolean 就去找实例方法,转写明确说它没什么属性和方法,这一节主要是认识创建方式和状态区别。
直接量方式
为什么需要:最简单、最常用的布尔值写法就是直接量。转写说“直接写个 true,直接写个 false 是不是就可以了”,还可以直接复制给变量。日常判断成立不成立,优先用这种方式。
怎么想:直接量得到的就是布尔值本身,是值状态,不是对象。转写里拿 Number 做对照:原始类型的数据有两种状态,值的状态和对象的状态;Boolean 也有类似情况。直接量属于最直接的值状态。
怎么做:写 true 或 false,需要时赋给变量:
js
let flag = true;
let empty = false;易错点:不要把直接量 false 和 new Boolean(false) 当成一回事。前者就是布尔值,后者按转写说法默认处于对象状态。调试时先看有没有 new,这是区分的关键。
Boolean 函数方式
为什么需要:有时手里不是布尔值,而是其他类型的数据,需要转成布尔值来判断。转写说,Boolean 的函数方式可以创建一个布尔值;除了直接写 true、false,写其他类型的数据也行,它会把其他类型的数据转换成布尔。
怎么想:Boolean(...) 是函数调用,不是 new Boolean(...)。转写把它叫“函数方式”,并和直接量方式并列。函数方式返回的是布尔值状态,适合做类型转换和条件判断。
怎么做:直接调用 Boolean:
js
Boolean(true); // true
Boolean(false); // false
Boolean(0); // 按 JS 规则转换
Boolean(""); // 按 JS 规则转换调试时不要只看代码长相,直接输出结果确认。转写里讲 Number 时也强调过:方法是返回结果,需要自己输出看;这里同样要养成“调用后打印返回值”的习惯。
易错点:不要把 Boolean(false) 写成 new Boolean(false)。前者是函数调用,得到布尔值;后者是构造函数方式,转写特别标注“默认对象状态”。这两个写法只差一个 new,含义不同。
Boolean 构造函数方式
为什么需要:构造函数方式用于认识“对象状态”。转写说,构造函数方式默认就处于对象状态,这和前面讲 Number 时 new Number 得到对象状态是一个道理。学习它不是因为它日常最常用,而是因为它能说明原始类型也有“值状态/对象状态”的区分。
怎么想:看到 new Boolean(true)、new Boolean(false),先判断:这是用构造函数创建对象,不是直接拿布尔值。转写还提到,不管什么状态,用的时候会自由转换,不耽误用;但判断时仍要保持清晰,别把对象状态当成直接量。
怎么做:按笔记写:
js
new Boolean(true);
new Boolean(false);调试时分别输出直接量、函数方式、构造函数方式,观察返回结果和类型。转写里强调“别被调试工具骗了,要保持清晰判断”,这里同样适用:先看写法,再看结果,不要只凭显示样子猜。
易错点:new Boolean(false) 不是直接量 false。转写说它默认是对象状态;在条件判断中要特别小心,不要以为写了 false 就一定是假值。实际开发中优先用直接量或 Boolean() 转换,new Boolean() 了解即可。
三种方式对比与调试思路
为什么需要:三种写法都能出现 Boolean 相关代码,但语义不同。转写反复提醒要分清“构造函数本身”和“实例”,也要分清“值状态”和“对象状态”。不对比,后面看文档和题目就容易混。
怎么想:按“有没有 new”“是不是函数调用”“是不是直接量”三条线判断。直接量最直接;Boolean(...) 是函数方式;new Boolean(...) 是构造函数方式。转写说 Boolean 实例没什么属性和方法,所以本节不展开 API,重点放在创建方式和状态区别。
怎么做:写一个小调试片段,逐个输出:
js
console.log(true);
console.log(Boolean(false));
console.log(new Boolean(false));先预测,再运行,再对照转写结论:直接量和函数方式得到布尔值状态;构造函数方式默认对象状态。遇到文档里的 Boolean.prototype 一类写法,要知道这是在说实例原型,不是在说构造函数本身的某个普通属性;转写也提醒过,文档有阅读门槛,先以课堂笔记为准,知识体系建立后再回头看。
易错点:不要编造 Boolean 实例方法;转写明确说它没什么属性和方法。不要把“构造函数本身”和“实例”混着判断;也不要把 new Boolean(false) 直接当成 false 使用。先把这三种创建方式记清,再谈后续原型和内置对象文档,思路才不会乱。
先明确本节要解决什么问题
学到 Number 这一节,最容易出现的不是“不会写方法”,而是分不清两件事:Number 实例能用的属性和方法,以及 Number 构造函数本身的属性和方法。转写稿里反复强调:看官方文档时,有的写 Number.prototype,有的直接写 Number.xxx,如果没有原型链意识,就会看不懂。所以本节的目标不是背 API,而是先建立判断标准:这个方法到底是给“数字实例”用的,还是给“Number 构造函数”用的。
怎么创建数字:三种方式与两种状态
创建数字常见有三种方式:直接量、Number 函数、new Number 构造函数。
js
let num = 100.904; // 直接量
let num2 = Number(100.904); // 函数方式
let num3 = new Number(100.904); // 构造函数方式为什么要讲这个?因为转写稿提到,new Number 默认得到的是对象状态,而前两种是值状态。但实际使用时不用过度担心:值状态调用方法时,会自动包装成对象,所以 num.toFixed(2) 照样能用。怎么想?先记住“值状态也能点方法”,但不要因此把 num 当成对象去理解。易错点是:不要因为 new Number 是对象,就以为它和普通数字完全一样;也不要把“能点方法”误认为“变量本身变成了对象”。
实例方法一:toFixed 保留小数位
toFixed 用来保留指定小数位数。转写稿里的例子是:
js
let num = 100.904;
console.log(num.toFixed(2)); // "100.90"
console.log(num.toFixed()); // "101"为什么需要它?因为金额、成绩、统计结果经常要固定小数位。怎么想?把参数理解成“保留几位小数”。怎么做?直接调用,但要注意它返回结果,不会自动打印,也不会改原变量。易错点有三个:第一,toFixed 是四舍五入,不是简单截断;第二,不写参数时保留整数,也是四舍五入;第三,返回值是字符串,例如 "100.90",不是数字 100.90。调试时看颜色能发现它是字符串。虽然它常能自动转成数字参与运算,但初学阶段一定要知道类型。
实例方法二:toString 转字符串与进制转换
toString 不只是“转字符串”,它还能按指定进制输出:
js
let num = 100.904;
console.log(num.toString()); // "100.904"
console.log(num.toString(2)); // 二进制字符串
console.log(num.toString(8)); // 八进制字符串
console.log(num.toString(16)); // 十六进制字符串
console.log(num.toString(36)); // 36 进制字符串为什么需要?因为转字符串是基础操作,而进制转换在调试、编码、理解底层表示时很有用。怎么想?参数表示进制,范围是 2 到 36。怎么做?把数字传进去,返回值仍是字符串。易错点:参数超过 36 会报错;不写参数时按十进制转字符串;它同样不改变原变量。转写稿里特别提醒:toString 返回的是字符串,数字只是字符串里的内容,不要看到“像数字”就以为类型还是 number。
构造函数属性:Number.MAX_VALUE 与 Number.MIN_VALUE
这两个不是实例属性,而是挂在 Number 构造函数上的:
js
console.log(Number.MAX_VALUE); // 可表示的最大数字
console.log(Number.MIN_VALUE); // 最小正数为什么需要?它们能表示 JavaScript 数字范围的边界。怎么想?看到前面写 Number.,就知道这是构造函数自己的属性,不是某个数字实例的。怎么做?直接通过 Number 访问。易错点:Number.MIN_VALUE 不是“最小负数”,而是最小正数,即无限接近 0 的正数;如果你写 num.MAX_VALUE,得到的是 undefined,因为实例上没有这个属性。转写稿里专门用这个例子说明“构造函数本身”和“实例”的区别。
用原型链解释:为什么方法能点出来
数字实例能调用 toFixed、toString,是因为这些方法在 Number.prototype 上;而 Number.MAX_VALUE 在 Number 构造函数自身上。判断时先问:文档写的是 Number.prototype.toFixed,还是 Number.MAX_VALUE?前者给实例用,后者给构造函数用。易错点是不要把“构造函数”和“实例”混着看。转写稿说,分析原型链时构造函数只是辅助,不要让它干扰判断。写笔记时也可以采用统一格式:实例方法直接写方法名,构造函数属性前加 Number.,这样复习时不容易混。
调试与总结
调试时不要只写 num.toFixed(2);,它只返回结果,不输出;要写 console.log(num.toFixed(2));。同时观察原变量:调用 toFixed、toString 后,num 本身仍是 100.904,没有变化。本节要掌握的不是一堆零散 API,而是三条线:数字的创建方式、实例方法与构造函数属性的区别、返回值类型和原变量是否改变。把这三条线理清,再看官方文档里的 Number.prototype 和 Number.xxx,就不会混乱。
先说明本节范围
本节笔记标题写作“3 String”,但给出的转写稿实际承接的是原型与原型链复习、引用类型与值类型、内置对象 Boolean 与 Number 的内容,并没有展开 String 的实例方法。因此下面不编造 String 知识点,只把转写稿中已经讲到的内容整理成教程;String 部分等后续笔记明确后再补。
原型与原型链:先分清 __proto__ 和 prototype
为什么需要:判断一个属性到底从哪来,必须知道对象沿哪条链查找;看 MDN 文档时,也要分清“实例方法”和“构造函数自身属性”。
怎么想:对象有 __proto__,构造函数有 prototype。函数本身也是对象,所以函数既有 prototype,也有 __proto__。函数.__proto__ 是它“作为对象”的原型;函数.prototype 是它“作为构造函数”创建出来的实例的原型。
怎么做:
js
function F() {}
F.prototype.a = 1;
const f = new F();
console.log(f.a); // 1
console.log(f.__proto__ === F.prototype); // true
console.log(F.__proto__ === Function.prototype); // true
console.log(Function.prototype.__proto__ === Object.prototype); // true易错点:Object.prototype 是顶层原型,它没有原型;Function.prototype 很特殊,它是所有函数的“实例原型”位置。调试工具显示可能很花,但判断时要回到“谁是谁的原型”。
原型链题目:小 f 和大 F 不是一条链
转写稿中的题目是:给 Object.prototype 加 a,给 Function.prototype 加 b,问实例 f 和构造函数 F 有没有这些方法。
js
function F() {}
Object.prototype.a = 'a';
Function.prototype.b = 'b';
const f = new F();
console.log(f.a); // 'a'
console.log(f.b); // undefined
console.log(F.a); // 'a'
console.log(F.b); // 'b'为什么:f 的链是 f -> F.prototype -> Object.prototype,所以有 a,没有 b;F 作为函数,链是 F -> Function.prototype -> Object.prototype,所以 a、b 都能找到。
易错点:不要因为 F 是 f 的构造函数,就把 F 自己的原型链混进 f 的查找链。构造函数在这里只是“辅助找原型”的身份,不参与实例的属性查找。
修改原型属性与重新赋值原型:一个生效,一个断开
js
function User() {}
User.prototype.name = 'A';
const u = new User();
User.prototype.name = 'B';
console.log(u.name); // B
User.prototype = { name: 'C' };
console.log(u.name); // B
const u2 = new User();
console.log(u2.name); // C怎么想:u.__proto__ 在 new User() 时已经指向了当时的 User.prototype 对象。后来改这个对象的 name,u 能看到;但把 User.prototype 整体赋值为新对象,只是让构造函数指向新对象,u.__proto__ 仍指向旧对象,所以断开。
易错点:User.prototype.name = 'B' 是改已有对象;User.prototype = { ... } 是换了一个新对象。两者效果不同。
值类型与引用类型:变量里到底存了什么
js
let a = 100;
let b = a;
b = 200;
console.log(a); // 100
let obj1 = { h: 100 };
let obj2 = obj1;
obj2.h = 200;
console.log(obj1.h); // 200
obj2 = { h: 400 };
console.log(obj1.h); // 200为什么:值类型的数据直接存在变量里;引用类型的变量存的是地址,真正对象在堆里。obj2.h = 200 是通过地址改同一个对象;obj2 = { h: 400 } 是给变量换了一个新地址。
判等也要看地址:
js
console.log('hello' === 'hello'); // true
console.log(100 === 100); // true
console.log({ name: '小乐' } === { name: '小乐' }); // false
const ar = [1, 2];
const ar2 = ar;
console.log(ar === ar2); // true易错点:两个对象“长得一样”不代表相等;每次写对象直接量、数组直接量,通常都是新对象。想保持同一对象,要赋给变量再复用。
函数传参:形参是局部变量,引用类型传的是地址
js
let name = 50;
function f1(name) {
name = 60;
console.log(name); // 60
}
f1(name);
console.log(name); // 50
let arr = [1, 2];
function f2(arr) {
for (let i = 0; i < arr.length; i++) {
arr[i] += 2;
}
console.log(arr); // [3, 4]
}
f2(arr);
console.log(arr); // [3, 4]怎么想:形参也是局部变量。值类型传参是复制值;引用类型传参是复制地址。改 arr[i] 会改到同一个数组;给形参重新赋值则只是让局部变量指向新对象。
易错点:函数内部找不到同名变量时会向外层作用域找;外层也没有就报错。不要把所有同名变量都当成同一个变量。
内置对象 Boolean:三种创建方式
js
let b1 = true;
let b2 = Boolean(0); // false
let b3 = new Boolean(false); // 对象状态
console.log(typeof b1); // boolean
console.log(typeof b2); // boolean
console.log(typeof b3); // object
console.log(b3.valueOf()); // false为什么:直接量和 Boolean() 得到值状态;new Boolean() 得到对象状态。对象状态在条件判断里要特别小心,通常要 valueOf() 取原始值。
易错点:new Boolean(false) 不是 false 这个原始值,而是一个对象。Boolean 本身没有太多要展开的实例方法,重点是理解它也在构造函数体系里。
Number 的实例方法与构造函数属性
js
let n = 100.907;
console.log(n.toFixed(2)); // "100.91"
console.log(n.toFixed()); // "101"
console.log(n.toString(2)); // 二进制字符串
console.log(n.toString(36)); // 36 进制字符串
console.log(Number.MAX_VALUE);
console.log(Number.MIN_VALUE); // 最小正数为什么:toFixed 用来保留指定小数位,采用四舍五入;toString(radix) 可以按 2 到 36 进制输出字符串。MAX_VALUE、MIN_VALUE 是构造函数自身的属性,不是实例属性。
易错点:toFixed 返回字符串,不改变原变量;toString 的参数超过 36 会报错;Number.MIN_VALUE 不是最小负数,而是最小正数。写文档总结时,实例方法直接写方法名,构造函数属性写成 Number.xxx,避免混淆。
收束:本节最该记住的三句话
第一,原型链只看属性查找顺序,构造函数身份不要混进实例链。第二,引用类型判等看地址,改属性和重新赋值是两件事。第三,内置对象要分清“实例方法”和“构造函数自身属性”,并记住返回值类型。
先认识 Math:内置对象,直接拿来用
为什么需要:写 JS 时经常要做绝对值、取整、随机数、最值等计算,如果每次都自己写逻辑,既麻烦又容易错。
怎么想:Math 是系统定义好的对象,是一个 Object 的实例,里面提前放好了常量和工具方法,直接通过 Math.xxx 使用。
怎么做:不要 new Math(),直接调用即可。本节笔记里的方法列表先保留:
js
Math.PI 圆周率
Math.abs() 返回绝对值
Math.pow() 返回次方数,两个参数
Math.sqrt() 返回平方根
Math.floor() 返回整数,向下取整
Math.ceil() 返回整数,向上取整
Math.round() 返回整数,四舍五入
Math.max() 返回参数中最大的,参数数量可以是任意个
Math.min() 返回参数中最小的,参数数量可以是任意个
Math.random() 返回一个随机数,范围是0到1,0可能会被取到,1不可能。易错点:Math 是对象,不是普通函数;Math.PI 是属性,不能写成 Math.PI()。转写稿里反复提醒“先判断身份,别把构造函数和实例混在一起”,放到这里就是:Math 是系统准备好的内置对象,不要按自定义构造函数那样去实例化。
常量与基础数学:PI、abs、pow、sqrt
Math.PI:为什么需要:圆周率是常用常量。怎么想:它是属性,不是方法。怎么做:直接 Math.PI。易错点:不要加括号调用。
Math.abs():为什么需要:取绝对值,避免正负号干扰。怎么想:传入一个数,返回非负数。怎么做:Math.abs(-5) 得到 5。易错点:它不会改变原变量,返回的是新结果,调试时要自己 console.log。
Math.pow():为什么需要:做次方运算。怎么想:两个参数,第一个是底数,第二个是指数。怎么做:Math.pow(2, 3) 得到 8。易错点:参数顺序别反,Math.pow(3, 2) 是 9。
Math.sqrt():为什么需要:求平方根。怎么想:传入一个数,返回它的平方根。怎么做:Math.sqrt(9) 得到 3。易错点:负数开平方在 JS 中会得到 NaN,不要误以为报错。
转写稿里讲内置对象时强调“方法调用得到返回值后要自己输出”,Math 也一样:Math.abs(-5) 本身不会自动显示,需要 console.log(Math.abs(-5)) 调试。
取整三兄弟:floor、ceil、round
Math.floor():为什么需要:向下取整,常用于分页、随机整数。怎么想:往数轴左边取最近的整数。怎么做:Math.floor(3.7) 得 3,Math.floor(-3.1) 得 -4。易错点:负数时不是“去掉小数”,而是更小。
Math.ceil():为什么需要:向上取整。怎么想:往数轴右边取最近的整数。怎么做:Math.ceil(3.1) 得 4,Math.ceil(-3.7) 得 -3。易错点:不要和 floor 记反。
Math.round():为什么需要:四舍五入。怎么想:看小数部分是否达到 0.5。怎么做:Math.round(3.5) 得 4,Math.round(3.4) 得 3。易错点:负数的舍入规则容易凭感觉判断错,实际调试输出最稳。
最值:max、min
Math.max():为什么需要:从多个数中找最大值。怎么想:参数数量任意。怎么做:Math.max(1, 9, 3) 得 9。易错点:如果参数中有不能转成数字的值,可能得到 NaN;传数组不能直接传,需要展开或 apply。
Math.min():为什么需要:找最小值。怎么想:与 max 相反。怎么做:Math.min(1, 9, 3) 得 1。易错点:空参数时 Math.max() 得 -Infinity,Math.min() 得 Infinity,初学不必死记,但调试时要留意。
随机数:random 与随机整数公式
为什么需要:抽奖、随机点名、游戏逻辑都要随机数。
怎么想:Math.random() 返回 [0, 1),即 0 可能取到,1 不可能取到。
怎么做:先保留两个公式:
取随机整数: 0 ~ n
js
Math.floor(Math.random() * (n+1))取随机整数: m ~ n
js
Math.floor(Math.random() * (n-m+1)) + m;推导过程:Math.random() 乘 (n+1) 后范围是 [0, n+1),再 Math.floor 向下取整,就能得到 0~n。如果范围是 m~n,先算区间长度 n-m+1,乘随机数并取整得到 0~n-m,最后加 m 平移到 m~n。
易错点:0~n 容易漏写 +1,导致取不到 n;m~n 容易把 n-m+1 写成 n-m。调试时多跑几次并输出结果,确认边界。
调试与同音字提醒
转写稿里“圆形”应理解为“原型”,“圆形料”是“原型链”,“实力”是“实例”,“per type”是“prototype”,“杠杠portto杠杠”是“proto”。这些不是 Math 的直接知识点,但提醒我们:看内置对象时先分清“对象本身”和“实例”。Math 的方法大多返回新值,不改变原变量;写完要自己输出验证。
本节定位:转写稿未展开 Date,先整理实际讲授内容
需要说明:本节笔记标题写作“5 Date”,但转写稿实际内容主要是原型与原型链回顾、原型链题目、值类型与引用类型、引用类型判等、引用传值题目,以及内置对象 Boolean、Number 的入门。转写稿中没有出现 Date 的具体 API、示例或调试过程。为避免编造,本文不虚构 Date 用法,而是按转写稿把本节真正讲到的内容整理成教程正文;这些判断框架也是后续学习 Date 等内置构造函数的基础。转写稿未提供图片,因此不插入图片。
原型与原型链:先分清“函数当对象”和“函数作构造函数”
为什么需要:判断一个属性到底从哪来,必须分清 __proto__、prototype 和构造函数三者关系。怎么想:对象自身有 __proto__,构造函数有 prototype;构造函数.prototype 是“实例的原型”,而 函数.__proto__ 是“函数作为对象时的原型”。怎么做:先判断当前是在看实例,还是在看构造函数本身。转写稿强调:函数.__proto__ 获取的是函数自身的原型;函数.prototype 获取的是该函数实例的原型。易错点:构造函数有两层身份,既是被 new 调用的函数,本身又是函数对象;分析原型链时不要让“构造函数那条链”干扰“实例那条链”。另外,顶层原型是 Object.prototype,它没有原型;Function.prototype 是特殊存在,所有函数的构造函数可理解为 Function,而 Function 自己构造自己。
原型链题目:先画链,再看属性查找
为什么需要:题目常把 Object.prototype、Function.prototype 和实例混在一起,靠猜容易错。怎么想:先回答“这个对象的构造函数是谁”,再写出它的原型,再往上一层写。转写稿中的基本判断是:小写实例 f 的原型链大致是 f -> F.prototype -> Object.prototype;大写函数 F 作为函数对象,其链大致是 F -> Function.prototype -> Object.prototype。怎么做:例如转写稿中的关键代码可整理为:
js
function F() {}
var f = new F();判断 f 有没有某属性,就沿 f -> F.prototype -> Object.prototype 找;判断 F 有没有某属性,就沿 F -> Function.prototype -> Object.prototype 找。易错点:调试工具输出可能“长得像”,但不能被显示形式骗了;一定要按原型链判断。转写稿还提醒:Function.prototype 不在数组实例 arr 的原型链上,它在“数组构造函数”那条链上,两条链要各看各的。
值类型与引用类型:栈、堆和赋值方式
为什么需要:同样写 b = a,数字和对象的结果可能完全不同。怎么想:值类型的数据整体存在栈结构里;引用类型的变量在栈里存地址,真正对象在堆里。怎么做:转写稿用两组代码对比:
js
var a = 100;
var b = a;
b = 200;
console.log(a); // 100
var obj1 = { h: 100 };
var obj2 = obj1;
obj2.h = 200;
console.log(obj1.h); // 200第一组是值传递,b 拿到的是 100 这个值;第二组是引用传递,也叫地址传递,obj1 和 obj2 存的是同一个对象地址。易错点:如果写成 obj2 = { h: 400 },这是给变量重新赋值,相当于让 obj2 指向新对象,不再和 obj1 指向同一个对象;但 obj2.h = 200 是修改对象属性,会影响同一个对象。转写稿还补充:hasOwnProperty 判断属性是否在自身上,而 in 会把原型上的属性也算进去。
引用类型判等:看地址,不看“长得一样”
为什么需要:两个对象字面量看起来一样,却不相等。怎么想:值类型看值是否相同;引用类型看地址是否相同。怎么做:
js
console.log('hello' === 'hello'); // true
console.log({ name: '小乐' } === { name: '小乐' }); // false
var ar1 = [1, 2];
var ar2 = ar1;
console.log(ar1 === ar2); // true易错点:每写一次对象字面量或数组字面量,都可能创建新对象;只有两个变量指向同一地址时才相等。转写稿强调,变量能保证对象数据的一致性:把对象赋给变量后反复使用这个变量,用的仍是同一个对象;直接写直接量,每写一遍就是新对象。
引用传值题目:形参是局部变量,改属性与重新赋值不同
为什么需要:函数传参时,容易把“修改对象属性”和“给形参重新赋值”混为一谈。怎么想:形参也是局部变量;传对象时传的是地址。怎么做:转写稿中的核心过程可整理为:
js
function f1(pp) {
pp.name = 'ls'; // 修改同一个对象的属性
pp = { name: 'aa' }; // 给形参重新赋值,指向新对象
}
var p = { name: 'zs' };
f1(p);
console.log(p.name); // 'ls'易错点:pp.name = 'ls' 会影响外部 p,因为二者指向同一对象;pp = { name: 'aa' } 只改变局部变量 pp 的指向,不影响外部 p。函数外部访问 pp 会报错,因为 pp 只存在于函数作用域中。
内置 Boolean 与 Number:实例方法与构造函数属性要分开
为什么需要:看官方文档时,常看到 Number.prototype.xxx 与 Number.xxx,不理解原型链就看不懂。怎么想:实例方法给实例用,构造函数自身属性给构造函数用。怎么做:转写稿中的 Number 示例可整理为:
js
var n = 100.907;
console.log(n.toFixed(2)); // '100.91',返回字符串,四舍五入
console.log(n.toString(2)); // 转成二进制字符串
console.log(Number.MAX_VALUE); // 最大数字
console.log(Number.MIN_VALUE); // 最小正数易错点:toFixed 返回字符串,不是数字;toString(2) 的参数表示进制,转写稿说明范围是 2 到 36;MAX_VALUE、MIN_VALUE 是 Number 构造函数自身的属性,不是实例属性,不能写成 n.MAX_VALUE。Boolean 也同理:直接量、函数调用、new Boolean 会得到不同状态,但转写稿指出它本身没有太多需要展开的实例方法。
小结:把判断顺序固定下来
遇到本节相关题目,先问三件事:这是实例还是构造函数本身?属性查找走哪条原型链?赋值时传的是值还是地址?把这三步固定下来,再去看 Date 等内置对象时,就能区分“实例方法”和“构造函数方法”,不会把 prototype、__proto__ 和构造函数混在一起。
课堂画面






代码示例
代码一:Boolean 与 Number 内置对象(三种创建方式 + 实例方法 + 构造函数属性)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Day10 内置对象 Boolean / Number</title>
</head>
<body>
<pre id="out"></pre>
<script>
const log = (...args) => {
document.getElementById('out').textContent += args.join(' ') + '\n';
};
// ========== 1. Boolean 的三种创建方式 ==========
// ① 直接量方式(值的状态)
let b1 = true;
let b2 = false;
// ② Boolean 函数方式:返回布尔值(值的状态)
log('Boolean(0) =', Boolean(0)); // false
log('Boolean("") =', Boolean('')); // false
log('Boolean("abc") =', Boolean('abc'));// true
// ③ Boolean 构造函数方式:默认是对象状态
let b3 = new Boolean(false);
log('new Boolean(false) =', b3); // Boolean {false}
log('typeof b3 =', typeof b3); // object
log('b3 在 if 里:', b3 ? '成立' : '不成立'); // 成立!易错点
// ========== 2. Number 实例的属性和方法 ==========
let num = 100.907;
// toFixed(n):保留 n 位小数,四舍五入,返回值是【字符串】
let r1 = num.toFixed(2);
log('num.toFixed(2) =', r1, '| typeof =', typeof r1); // 100.91 string
log('num.toFixed() =', num.toFixed()); // 101(不传参保留整数)
log('100.904.toFixed() =', (100.904).toFixed()); // 101
// toString(进制):返回指定进制形式的字符串,参数范围 2 ~ 36
log('num.toString(2) =', num.toString(2));
log('num.toString(8) =', num.toString(8));
log('num.toString(16) =', num.toString(16));
log('num.toString(36) =', num.toString(36));
// num.toString(37); // 报错:参数必须在 2~36 之间
// 调用方法不会改变原变量的值
log('num 本身 =', num); // 100.907
// ========== 3. Number 构造函数本身的属性 ==========
log('Number.MAX_VALUE =', Number.MAX_VALUE); // 最大数
log('Number.MIN_VALUE =', Number.MIN_VALUE); // 最小正数
log('num.MAX_VALUE =', num.MAX_VALUE); // undefined(实例上没有)
</script>
</body>
</html>要点解释与易错点:
Boolean(0)、Boolean('')属于函数方式,返回的是值的状态;new Boolean(false)属于构造函数方式,默认是对象状态。- 易错点:
new Boolean(false)是一个对象,对象永远“真”,所以if (new Boolean(false))走的是成立分支,别拿它做判断。 toFixed()的返回值是字符串而不是数字,只是字符串里装的内容是数字,需要运算时靠 JS 自动转换。toFixed()是四舍五入,不传参数就保留整数位。toString(进制)的参数必须在2~36之间,超过会直接报错;它同样返回字符串。MAX_VALUE/MIN_VALUE是构造函数本身的属性,要写Number.MAX_VALUE,写num.MAX_VALUE得到undefined;另外MIN_VALUE是“最小正数”,不是最小数。- 方法调用结束后原变量
num的值不变,结果只以返回值形式给出。
代码二:Math 常用方法与取随机整数公式
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Day10 Math 内置对象</title>
</head>
<body>
<pre id="out"></pre>
<script>
const log = (...args) => {
document.getElementById('out').textContent += args.join(' ') + '\n';
};
// Math 是系统定义好的对象,是 Object 的实例
log('Math.PI =', Math.PI); // 圆周率
log('Math.abs(-5) =', Math.abs(-5)); // 绝对值
log('Math.pow(2, 10) =', Math.pow(2, 10)); // 次方,两个参数
log('Math.sqrt(16) =', Math.sqrt(16)); // 平方根
log('Math.floor(1.9) =', Math.floor(1.9)); // 1 向下取整
log('Math.ceil(1.1) =', Math.ceil(1.1)); // 2 向上取整
log('Math.round(1.5) =', Math.round(1.5)); // 2 四舍五入
log('Math.max(1, 9, 3) =', Math.max(1, 9, 3)); // 参数个数任意
log('Math.min(1, 9, 3) =', Math.min(1, 9, 3));
// Math.random():0 ~ 1,能取到 0,取不到 1
log('Math.random() =', Math.random());
// 取随机整数:0 ~ n
function random0ToN(n) {
return Math.floor(Math.random() * (n + 1));
}
// 取随机整数:m ~ n
function randomMToN(m, n) {
return Math.floor(Math.random() * (n - m + 1)) + m;
}
// 验证范围
let arr1 = [];
for (let i = 0; i < 10; i++) arr1.push(random0ToN(9));
log('random0ToN(9) 十次 =', arr1.join(','));
let arr2 = [];
for (let i = 0; i < 10; i++) arr2.push(randomMToN(5, 8));
log('randomMToN(5, 8) 十次 =', arr2.join(','));
</script>
</body>
</html>要点解释与易错点:
- 取
0 ~ n的整数关键在n + 1:Math.random()取不到 1,乘n+1才能取到 n。 - 取
m ~ n的整数,先算区间长度n - m + 1,乘完向下取整后再+ m平移,漏掉+ m就变成0 ~ (n-m)了。 - 区间长度必须是
n - m + 1,写成n - m就永远取不到 n。 Math.floor和Math.ceil方向相反,Math.round是四舍五入,三者别混用。Math.random()可能取到 0,但不可能取到 1。
代码三:值类型与引用类型的区别(赋值、判等、传参)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Day10 值类型与引用类型</title>
</head>
<body>
<pre id="out"></pre>
<script>
const log = (...args) => {
document.getElementById('out').textContent += args.join(' ') + '\n';
};
// ========== 1. 值类型(原始类型):值传递 ==========
let a = 100;
let b = a; // 把 a 的“值”复制给 b
b = 200;
log('a =', a); // 100
log('b =', b); // 200
// ========== 2. 引用类型(对象类型):地址传递 ==========
let obj1 = { h: 100 };
let obj2 = obj1; // 传的是地址,两个变量指向同一个对象
obj2.h = 200; // 改的是同一个对象里的属性
log('obj1.h =', obj1.h); // 200
// 给 obj2 整体重新赋值:相当于新建对象,与 obj1 断开
obj2 = { h: 400 };
log('obj1.h =', obj1.h); // 200 不变
log('obj2.h =', obj2.h); // 400
// ========== 3. 判等方式 ==========
log("'hello' === 'hello' →", 'hello' === 'hello'); // true,看值
log("{name:'小乐'} === {name:'小乐'} →",
{ name: '小乐' } === { name: '小乐' }); // false,看地址
let ar = [1, 2];
let ar2 = ar;
log('ar === ar2 →', ar === ar2); // true,地址相同
log('[1,2] === [1,2] →', [1, 2] === [1, 2]); // false,两个新地址
// ========== 4. 函数传参也遵守同样规则 ==========
function f1(ar) { // 形参是局部变量,但拿到的是地址
for (let i = 0; i < ar.length; i++) ar[i] += 2;
}
let arr = [1, 2];
f1(arr);
log('f1(arr) 之后 arr =', arr.join(',')); // 3,4
function f2(x) { // 值传递,改的是形参自己
x = 30;
}
let num = 10;
f2(num);
log('f2(num) 之后 num =', num); // 10
</script>
</body>
</html>要点解释与易错点:
- 值类型(原始类型)在栈中直接存值,赋值时复制的是值本身,所以改
b不影响a。 - 引用类型在堆中存数据,变量里存的是地址,赋值时复制的是地址,所以
obj2.h = 200会影响obj1.h。 - 关键区别:修改属性(
obj2.h = 200)改的是同一个对象;整体重新赋值(obj2 = { h: 400 })是新建对象,两条地址线断开,之后互不影响。 - 引用类型判等看地址:长得一样不算相等,只有指向同一个对象才
===成立;这也是“变量能保证数据一致性”的原因——用直接量每写一遍就是一个新对象。 - 函数传参同样遵守上述规则:传对象进去,函数内部改元素/属性会影响到外面;但函数内部给形参重新赋值,外面不受影响。
常见错误
- 现象:
if (new Boolean(false))走进真分支 → 原因:new Boolean(false)是对象,对象恒为真 → 修法:用false直接量或Boolean(false)。 - 现象:
(1.005).toFixed(2) + 1得到字符串拼接 → 原因:toFixed()返回字符串 → 修法:需要数值时用Number(...)或+转换。 - 现象:
Math.floor(Math.random() * n)总取不到n→ 原因:区间公式端点写错 → 修法:0~n用Math.floor(Math.random() * (n + 1))。 - 现象:整体重写
构造函数.prototype后,旧实例访问不到新原型方法 → 原因:实例的原型链接在new时已绑定,重写 prototype 会换成新对象 → 修法:在旧原型上增删属性,或重新实例化。 - 现象:两个内容相同的对象/数组用
===比较为false→ 原因:引用类型比较的是地址 → 修法:比较同一引用,或按业务逐属性/逐元素比较。 - 现象:函数内改了与全局同名的形参/局部变量,外部不变 → 原因:作用域遮蔽,形参是局部变量 → 修法:避免同名,或通过返回值显式传出结果。
练习
- 判断输出并说明原因:js
console.log(new Boolean(false) ? '真' : '假'); console.log((10.987).toFixed(2) + 1); - 写一个函数
randomInt(m, n),返回m~n的随机整数,并说明为什么不能直接用Math.round(Math.random() * n)。 - 判断以下代码输出,并画出简要原型链:js
function F() {} Array.prototype.a = 1; Function.prototype.b = 2; const arr = []; console.log(arr.a, arr.b); - 判断输出并解释:js
const o1 = { n: 1 }; const o2 = o1; o2.n = 2; console.log(o1.n); o2 = { n: 3 }; console.log(o1.n); - 改写下面代码,使
user实例在构造函数 prototype 被整体替换后仍能拿到新方法:jsfunction User() {} const u = new User(); User.prototype = { say() { return 'hi'; } }; console.log(u.say());
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 原型和原型链总结 | 19:09 |
| 2 | 原型链题目讲解 | 14:33 |
| 3 | 值类型和引用类型 | 17:41 |
| 4 | 引用类型的判等方式 | 10:03 |
| 5 | 引用传值题目分析 | 15:42 |
| 6 | 内置对象-Boolean | 06:10 |
| 7 | 内置构造函数-Number | 10:16 |
小结
- 内置构造函数优先用直接量或函数转换,
new包装对象少用,避免真假判断陷阱。 - 分清“实例方法”和“构造函数静态方法”,调用位置写错常得到
undefined或报错。 toFixed()、toString(radix)等返回新值,不改变原数字,且要注意返回类型。- 随机整数必须同时检查区间公式和端点,
Math.random()本身不保证业务范围。 - 原型链判断先看实例自身,再看原型;构造函数只作辅助,不要混入实例原型链。
- 整体替换
prototype会断开旧实例的原型链接,新增/修改原型属性更安全。 - 引用类型赋值传地址:改属性会互相影响,重新赋值会断开联系;比较默认看地址。
- 作用域查找优先局部,形参和局部变量会遮蔽全局变量,排错时先确认变量来源。