JavaScript 基础知识复习(二)

9 阅读17分钟

一、bind 连续执行多次后的 this 指向

在 JavaScript 中,可以使用 bind() 方法来改变函数执行时的 this 上下文。

如果连续多次对同一个函数使用 bind() 方法,那么每次调用 bind() 方法都会返回一个新的函数,但是这些新函数的 this 上下文都是相同的,指向第一个绑定的对象。

根据 ECMAScript 规范,bind() 方法返回一个"绑定函数"(Bound Function),这个绑定函数有内部槽:

  • [[BoundTargetFunction]]:原始函数
  • [[BoundThis]]:绑定的 this 值

关键特性:一旦使用 bind() 绑定了 this,后续的 bind() 调用无法改变已经绑定的 this。

function greet() {
    console.log(this.name);
}

const obj1 = { name: 'obj1' };
const obj2 = { name: 'obj2' };

// 第一次绑定
const bound1 = greet.bind(obj1);
bound1(); // 输出: obj1

// 第二次绑定(无效)
const bound2 = bound1.bind(obj2);
bound2(); // 仍然输出: obj1(不是 obj2)

// 绑定 null 的情况
const bound3 = greet.bind(null);
bound3(); // 输出: undefined(严格模式)或全局对象的 name 属性

// 对绑定 null 的函数再次绑定
const bound4 = bound3.bind(obj2);
bound4(); // 输出: undefined(仍然无法改变)

注意:如果在使用 bind() 方法时,最终绑定的对象为 null 或 undefined,那么 this 上下文,在非严格模式下会指向全局对象 window(浏览器环境)或 global(Node.js 环境),在严格模式下保持为 null 或 undefined,这可能会导致不可预期的行为。


二、0.1+0.2 为什么不等于 0.3

在 JavaScript 中,浮点数是以 IEEE 754 标准的二进制浮点数表示的,它采用二进制的形式来表示实数。而二进制无法精确地表示某些十进制小数,例如 0.1 和 0.2,因为它们在二进制下是无限循环的小数,而浮点数只有 64 位的精度。因此,当在 JavaScript 中执行 0.1 + 0.2 的计算时,由于无法精确表示这两个数字,它们会被转换成最接近的可表示二进制数,然后再进行计算。这会导致一个微小的舍入误差,使得结果不等于 0.3。

2.1 解决办法

2.1.1 使用容差范围比较

// 设置一个极小的误差范围(容差)
const EPSILON = 1e-10;
function areEqual(a, b) {
    return Math.abs(a - b) < EPSILON;
}
console.log(areEqual(0.1 + 0.2, 0.3)); // true

// 使用Number.EPSILON(ES6+)
function areEqual(a, b) {
    return Math.abs(a - b) < Number.EPSILON;
}
console.log(areEqual(0.1 + 0.2, 0.3)); // true

2.1.2 转换为整数计算

// 将小数转换为整数进行计算,再转回小数
function addDecimals(a, b) {
    const multiplier = Math.pow(10, Math.max(
        a.toString().split('.')[1]?.length || 0,
        b.toString().split('.')[1]?.length || 0
    ));
    return (a * multiplier + b * multiplier) / multiplier;
}
console.log(addDecimals(0.1, 0.2) === 0.3); // true

2.1.3 四舍五入处理

可以使用 toFixed() 将结果四舍五入到指定小数位数,需要注意的是,toFixed() 方法返回一个字符串类型的结果,因此需要注意类型转换。

const result = (0.1 + 0.2).toFixed(1);
console.log(parseFloat(result) === 0.3); // true

2.1.4 使用 decimal.js、big.js 等专门处理精度的库

import Decimal from 'decimal.js';

const sum = new Decimal(0.1).plus(0.2);
console.log(sum.equals(0.3)); // true
console.log(sum.toNumber()); // 0.3

三、Symbol 的作用

Symbol 是在 ES6 新增的基础数据类型,它的主要作用是创建一个唯一的标识符,用于对象属性名的命名、常量的定义等场景。

每个 Symbol 都是唯一的,可以用作对象的属性名,这样可以避免属性名冲突的问题。

const s1 = Symbol();
const s2 = Symbol();
const obj = {
    [s1]: 'hello',
    [s2]: 'world'
};

Symbol 可以用于实现一些常量或枚举值,这些值是不可修改和重复的。

const Colors = {
    Red: Symbol('Red'),
    Green: Symbol('Green'),
    Blue: Symbol('Blue')
};
console.log(Colors.Red); // Symbol(Red)

由于 Symbol 是一种基础数据类型,所以它具有很高的性能和可靠性,可以用于需要创建和使用高效和安全的标识符的场景。

注意:Symbol 还有一个重要的特点是,它不会出现在 for...infor...ofObject.keys()Object.getOwnPropertyNames() 等遍历对象属性的方法中,因此可以用来定义一些不希望被遍历到的属性,例如一些内部实现细节或隐藏属性。但可以使用 Object.getOwnPropertySymbols()Reflect.ownKeys() 获取到 Symbol 类型的属性名。


四、typeof null 为什么是 object

typeof null 的结果为 "object",这是 JavaScript 语言的一个历史遗留问题。在 JavaScript 最初的版本中,使用 32 位的值表示一个变量,其中前 3 位用于表示值的类型。000 表示对象,010 表示浮点数,100 表示字符串,110 表示布尔值,和其他的值都被认为是指针。在这种表示法下,null 被解释为一个全零的指针,也就是说它被认为是一个空对象引用,因此 typeof null 的结果就是 "object"

在判断变量是否为 null 时,建议使用严格相等运算符(===)进行判断。


五、ES6 的新特性

ES6(ECMAScript 2015)是 JavaScript 的一个新版本,引入了很多新的特性和语法,其中一些比较常用的包括:

  1. 块级作用域:通过 letconst 声明的变量只在当前块级作用域中有效。
  2. 箭头函数:使用 => 符号定义的函数,具有简化的语法和自动绑定 this 上下文的特点。
  3. 模板字符串:使用反引号 `${} 操作符,可以方便地拼接字符串和变量。
  4. 解构赋值:可以将数组或对象的值解构赋给变量。
  5. 类和继承:引入了 classextends 关键字,使得 JavaScript 支持面向对象编程。
  6. Promise 和 async/await:用于处理异步编程的新特性。

六、箭头函数和普通函数有何区别?

关于箭头函数和普通函数的区别,主要有以下几点:

6.1 this 上下文

箭头函数没有自己的 this 上下文,它的 this 上下文继承自外部作用域,因此不能使用 call()apply()bind() 方法改变 this 上下文。

6.2 arguments 对象

箭头函数没有自己的 arguments 对象,如果需要获取函数参数,可以使用 rest 参数或者展开运算符。

箭头函数没有自己的 arguments

const arrowFunc = (a, b) => {
    console.log('箭头函数的 arguments:', arguments); // 这里访问的是外层作用域的 arguments
    console.log('a:', a, 'b:', b);
}
arrowFunc(1, 2, 3, 4);
// 在严格模式下会报错:ReferenceError: arguments is not defined

箭头函数继承外层函数的 arguments

function outerFunction() {
    console.log('外层的 arguments:', arguments); // [10, 20, 30]
    
    const innerArrow = () => {
        console.log('箭头函数内部的 arguments:', arguments); // 继承外层的 arguments
    };
    
    innerArrow(40, 50, 60); // 这里的参数不会被箭头函数自己的 arguments 接收
}
outerFunction(10, 20, 30);
// 外层的 arguments: Arguments(3) [10, 20, 30]
// 箭头函数内部的 arguments: Arguments(3) [10, 20, 30]

使用 rest 参数获取所有参数

const arrowFuncWithRest = (...args) => {
    console.log('使用 rest 参数:', args);
    console.log('参数个数:', args.length);
    console.log('第一个参数:', args[0]);
    
    // 可以像数组一样使用
    args.forEach((arg, index) => {
        console.log(`参数 ${index}:`, arg);
    });
};
arrowFuncWithRest(1, 2, 3, 4, 5);

rest 参数与展开运算符配合使用

const maxValue = (...values) => {
    return Math.max(...values); // 展开运算符
};
const numbers = [10, 5, 8, 20, 3];
console.log('最大值:', maxValue(...numbers)); // 20

6.3 构造函数

箭头函数不能作为构造函数使用,不能使用 new 关键字创建对象。


七、箭头函数能当构造函数吗?

根据规范来说,箭头函数是没有 [[Construct]] 方法的,因此不能使用 new 关键字创建对象。如果强制使用 new 关键字调用箭头函数,会抛出一个类型错误 TypeError: xxx is not a constructor

因此,一般来说箭头函数不应该用于创建对象,而应该用于函数式编程和简化回调函数等场景。


八、Promise.all 和 Promise.allSettled 的区别

Promise.all()Promise.allSettled() 是两个 JavaScript Promise 相关的方法,它们都用于处理多个 Promise 对象的并发执行。

8.1 区别

8.1.1 Promise.all() 方法

返回的 Promise 对象在所有的 Promise 对象都 resolve 之后,才会 resolve 并返回一个由所有 Promise 返回值组成的数组。如果其中有一个 Promise 被 reject,则会立即 reject 并返回相应的错误信息。

简单来说:只有所有 Promise 都成功了才算成功,有一个失败了就算失败。

8.1.2 Promise.allSettled() 方法

返回的 Promise 对象在所有的 Promise 对象都 resolve 或 reject 之后,才会 resolve 并返回一个由所有 Promise 状态对象组成的数组,每个状态对象包含一个 status 字段表示 Promise 状态,和一个 valuereason 字段表示 Promise 的返回值或错误信息。

简单来说:它会等到所有 Promise 都执行完毕,无论成功还是失败,都会把每个 Promise 的状态信息收集到一个数组里面返回。

8.2 总结

Promise.all() 方法在需要所有 Promise 都成功的情况下使用,而 Promise.allSettled() 方法则适用于需要知道每个 Promise 的执行结果的情况下使用。

8.3 补充:Promise.race() 和 Promise.any()

8.3.1 Promise.race() 方法

返回的 Promise 对象在第一个 Promise 对象 resolve 或 reject 后,就会立即 resolve 或 reject,并返回第一个完成的 Promise 的结果。

简单来说:谁先完成就返回谁的结果,无论成功还是失败。

const promise1 = new Promise((resolve) => setTimeout(resolve, 500, 'one'));
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'two'));

Promise.race([promise1, promise2]).then((value) => {
    console.log(value); // 输出: "two"(因为 promise2 先完成)
});

8.3.2 Promise.any() 方法

返回的 Promise 对象在第一个 Promise 对象 resolve 后,就会立即 resolve 并返回第一个成功的 Promise 的结果。只有当所有 Promise 都 reject 时,才会 reject 并返回一个 AggregateError 错误。

简单来说:只要有一个成功就算成功,全部失败才算失败。

const promise1 = Promise.reject('error1');
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'success'));
const promise3 = Promise.reject('error3');

Promise.any([promise1, promise2, promise3]).then((value) => {
    console.log(value); // 输出: "success"
}).catch((error) => {
    console.log(error); // 只有全部失败才会执行
});

四种 Promise 方法的对比总结

  • Promise.all():全部成功才成功,一个失败就失败
  • Promise.allSettled():等待所有完成,返回所有结果
  • Promise.race():第一个完成就返回(无论成功失败)
  • Promise.any():第一个成功就成功,全部失败才失败

九、判断数据类型的方法

9.1 方法一:使用 typeof 操作符

typeof 操作符可以返回一个值的数据类型,它适用于除了 null 以外的所有值,因为 typeof null 返回 "object"

记得返回的字符串是小写的,跟后面的 Object.prototype.toString 的不一样。

9.2 方法二:使用 instanceof 操作符

instanceof 操作符可以判断一个对象是否是某个构造函数的实例。

object instanceof constructor

9.3 方法三:使用 Object.prototype.toString 方法

可以返回一个值的内部类型,它适用于所有值,包括 null 和 undefined。

注意:使用 Object.prototype.toString 方法判断基本类型值时,返回的是其包装对象的类型,而不是基本类型本身的类型。

Object.prototype.toString.call(value)

返回值是一个形如 [object Type] 的字符串,其中 Type 表示值的内部类型。

Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call(123); // "[object Number]"
Object.prototype.toString.call("abc"); // "[object String]"
Object.prototype.toString.call(Symbol("foo")); // "[object Symbol]"
Object.prototype.toString.call({}); // "[object Object]"
Object.prototype.toString.call(function () {}); // "[object Function]"
Object.prototype.toString.call([]); // "[object Array]"
Object.prototype.toString.call(new Date()); // "[object Date]"
Object.prototype.toString.call(/abc/); // "[object RegExp]"

9.4 方法四:特定类型的判断

Array.isArray 判断数组类型、isNaN 判断是否为 NaN、Number.isInteger 判断是否为整数等。

9.5 方法五:使用 Object.is() 方法

Object.is() 方法用于判断两个值是否为同一个值,它与 === 的主要区别在于对 NaN 和 -0 的处理:

// Object.is() 与 === 的区别
console.log(NaN === NaN); // false
console.log(Object.is(NaN, NaN)); // true

console.log(-0 === +0); // true
console.log(Object.is(-0, +0)); // false

// 其他情况与 === 相同
console.log(Object.is(1, 1)); // true
console.log(Object.is('foo', 'foo')); // true
console.log(Object.is(null, null)); // true
console.log(Object.is(undefined, undefined)); // true

使用场景:当需要精确判断 NaN 或区分 -0 和 +0 时,使用 Object.is()

不同的判断方式适用于不同的场景,具体选择哪一种方式应根据实际情况而定。


十、substring 和 substr 的区别

substring()substr() 都是 JavaScript 字符串的方法,用于截取字符串的一部分。

10.1 区别在于参数的不同

10.1.1 substring() 方法

接收两个参数,起始位置和结束位置。它截取从起始位置到结束位置之间的字符,包括起始位置的字符,但不包括结束位置的字符。如果省略第二个参数,则截取到字符串末尾。

10.1.2 substr() 方法

接收两个参数,起始位置和截取的字符数。它从起始位置开始截取指定数量的字符,如果省略第二个参数,则截取到字符串末尾。

注意substr() 方法被普遍建议弃用,虽然它未被完全从标准中移除,但已被标记为遗留函数,并可能在未来被删除。


十一、== 和 === 的区别

11.1 == 运算符

进行比较时,会先进行类型转换,然后再比较两个值是否相等。

类型转换的规则比较复杂,但可以简单地概括为以下几点:

  1. 如果两个值类型相同,则直接比较它们的值。
  2. 如果一个值是 null,另一个值是 undefined,则它们相等。
  3. 如果一个值是数字,另一个值是字符串,则将字符串转换为数字后再比较。
  4. 如果一个值是布尔值,另一个值是非布尔值,则将布尔值转换为数字后再比较。
  5. 如果一个值是对象,另一个值是数字、字符串或布尔值,则将对象转换为原始值后再比较。

11.2 === 运算符

进行比较时,不进行类型转换,只有当两个值的类型和值都相等时才会返回 true。

11.3 总结

一般来说,建议优先使用 === 运算符进行比较,因为它可以避免类型转换的问题,更加严格和安全。


十二、异步加载脚本如何实现?

在 Web 应用中,JavaScript 脚本的异步加载可以通过以下方式来实现:

12.1 动态创建 script 标签

动态创建 <script> 标签,并设置其 src 属性为需要加载的脚本 URL。这种方式可以通过设置 onload 或 onreadystatechange 事件来检测脚本是否加载完成。

const script = document.createElement('script');
script.src = 'path/to/script.js';
script.onload = function() {
    // 脚本加载完成后执行的回调函数
};
document.body.appendChild(script);

12.2 使用 XMLHttpRequest 或 Fetch API

使用 XMLHttpRequest 对象或 Fetch API 发送异步请求,并在请求成功后将响应文本解析为 JavaScript 代码,然后使用 eval() 函数或 Function() 构造函数来执行脚本。

const xhr = new XMLHttpRequest();
xhr.open('GET', 'path/to/script.js');
xhr.onload = function() {
    const script = document.createElement('script');
    script.textContent = xhr.responseText;
    document.head.appendChild(script);
};
xhr.send();

12.3 使用 async 或 defer 属性

在 script 标签中,使用 async 或 defer 属性,浏览器会异步下载。

  • async:不保证执行顺序,下载完就执行
  • defer:下载后在 DOM 解析完成后、DOMContentLoaded 事件触发前,按 defer 脚本声明的顺序执行

十三、异步加载脚本与同步加载脚本的区别

相比于同步加载脚本,异步加载具有以下区别:

  1. 异步加载可以提高页面的加载速度和响应性能,避免因 JavaScript 阻塞而造成页面卡顿的情况。
  2. 异步加载可以避免因加载脚本而造成的阻塞情况,使页面的其他资源可以更快地加载和呈现。
  3. 异步加载可以更灵活地控制脚本的加载顺序和执行时间,可以根据页面需要动态加载和卸载脚本,提高页面的可维护性和可扩展性。
function loadScript(src, callback) {
    const script = document.createElement('script');
    script.src = src;
    script.onload = () => callback && callback(null, script);
    script.onerror = () => callback && callback(new Error(`加载失败: ${src}`));
    document.head.appendChild(script);
    return script; // 返回 script 元素,便于后续移除
}

function unloadScript(scriptElement) {
    if (scriptElement && scriptElement.parentNode) {
        scriptElement.parentNode.removeChild(scriptElement);
        console.log('脚本标签已从 DOM 中移除');
    }
}

// 注意,移除标签不会撤销脚本执行时定义的全局变量或函数,
// 如果希望彻底"卸载"脚本的影响,需要配合模块化设计,使用脚本提供的清理方法

十四、for in/for of的区别

14.1 for...in

循环用于遍历对象的可枚举属性,它会将对象的每个属性名称(或键名)作为迭代变量来遍历。

注意for...in 循环遍历的是对象的可枚举属性,包括自有属性和继承属性。因此,它并不适用于遍历数组和类数组对象。

14.2 for...of

循环用于遍历可迭代对象的元素,它会将对象的每个元素作为迭代变量来遍历。可以用于如数组、字符串、Set、Map 等。

注意for...of 循环只能遍历实现了迭代器接口(Iterator)的对象,因此它不适用于普通的对象。此外,它遍历的是对象的元素值,而不是键名或属性名。

14.3 总结

for...in 适用于遍历对象的属性名,而 for...of 适用于遍历数组、字符串等可迭代对象的元素值。


十五、splice 和 slice 会改变原数组吗?

15.1 splice() 方法

可以在数组中添加、删除或替换元素,并返回被删除的元素,它会改变原数组

15.2 slice() 方法

是从原数组中返回指定开始和结束位置的元素组成的新数组,它不会改变原数组


十六、怎么删除数组最后一个元素?

16.1 方法一:使用 pop() 方法

删除并返回数组的最后一个元素

arr.pop();

16.2 方法二:使用 splice() 方法

删除数组的最后一个元素

arr.splice(-1, 1);

16.3 方法三:使用 slice() 方法

创建一个新的数组,包含除了最后一个元素以外的所有元素

const newArr = arr.slice(0, -1);

十七、数组 forEach 能否结束循环?

数组的 forEach 方法默认不支持提前结束循环,即无法使用类似于 breakreturn 的语法来跳出循环。

但是可以使用抛出异常的方式来达到提前结束循环的效果。需要注意的是,这种方式并不常用,通常可以使用 for 循环或 someevery 等数组方法来替代。


十八、合并对象的方法

18.1 方法一:使用 Object.assign()

const mergedObj = Object.assign({}, obj1, obj2);

18.2 方法二:使用扩展运算符

const mergedObj = { ...obj1, ...obj2 };

十九、判断一个对象是不是空对象的方法

19.1 方法一:使用 Object.keys()

使用 Object.keys() 方法获取对象的属性列表,然后判断列表长度是否为0。性能最快

Object.keys(obj).length === 0

19.2 方法二:使用 JSON.stringify()

使用 JSON.stringify(obj) === '{}' 来判断。性能最慢

19.3 方法三:使用 Object.getOwnPropertyNames()

包括了不可枚举属性。

Object.getOwnPropertyNames(obj).length === 0

19.4 方法四:使用 for...in 循环

const obj = {};
let isEmpty = true;
for (const prop in obj) {
    // for...in 循环会遍历对象原型链上的所有可枚举属性,而不仅仅是对象自身的属性
    if (obj.hasOwnProperty(key)) {
        isEmpty = false;
        break;
    } 
}
if (isEmpty) {
    console.log('obj is empty');
}

对象有可能重写了 hasOwnProperty 方法,更安全的方式是使用 Object.prototype.hasOwnProperty

function isEmptyObject(obj) {
    if (obj == null || typeof obj !== 'object') {
        return false;
    }
    
    for (let key in obj) {
        // 使用更安全的检查方式
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
            return false;
        }
    }
    return true;
}

19.5 方法五:使用 Reflect.ownKeys()

包括了 Symbol 属性。

Reflect.ownKeys(obj).length === 0

19.6 完整健壮的解决方案

function isEmptyObject(obj) {
    // 1. 检查是否为null或undefined
    if (obj == null) return false;
    
    // 2. 检查是否为对象类型
    if (typeof obj !== 'object' || Array.isArray(obj)) {
        return false;
    }
    
    // 3. 检查是否为空对象
    return Object.keys(obj).length === 0;
}

二十、requestAnimationFrame 和 requestIdleCallback 的作用

requestAnimationFramerequestIdleCallback 都是用于在浏览器中执行动画或其他高性能任务的 API。

20.1 requestAnimationFrame

浏览器提供的一种动画帧请求机制,会在浏览器下一次绘制之前执行指定的回调函数。

这样做的好处是可以在浏览器下一次绘制时,让浏览器自动完成一些复杂的计算和渲染工作,从而避免了浏览器在短时间内重复执行相同的任务。

使用 requestAnimationFrame 可以实现更加流畅的动画效果,同时也可以减少页面的闪烁和卡顿。

function animate() {
    // 在这里编写动画逻辑
    requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

20.2 requestIdleCallback

一个相对较新的 API,它的作用是在浏览器空闲时执行指定的回调函数。

让开发者能够在浏览器空闲时,进行一些比较耗时的任务,例如计算和渲染。

这样做的好处是可以提高网页的性能和响应速度,同时也可以避免阻塞浏览器的主线程导致用户体验不佳。

注意的是,requestIdleCallback 的回调函数接受一个 IdleDeadline 参数,它包含了当前空闲时间的相关信息。开发者可以通过这个参数,根据浏览器的空闲时间进行任务调度和优化。

function doWork(deadline) {
    while (deadline.timeRemaining() > 0) {
        // 在这里编写任务逻辑
    }
    if (还有任务需要执行) {
        requestIdleCallback(doWork);
    }
}
requestIdleCallback(doWork);

20.3 总结

requestAnimationFrame 适用于需要在下一次绘制之前执行的动画任务,而 requestIdleCallback 则适用于需要在浏览器空闲时执行的耗时任务。


二十一、canvas 怎么判断点在图形内?

判断一个点是否在 Canvas 中的图形内,通常需要使用 Canvas 提供的 API 或数学公式来实现。

21.1 对于简单的图形

如矩形、圆形等,可以使用 Canvas API 中的 isPointInPath 方法进行判断:

21.1.1 判断点是否在矩形内

使用 ctx.rect() 绘制矩形,再使用 ctx.isPointInPath(x, y) 方法判断点是否在矩形内。

21.1.2 判断点是否在圆形内

使用 ctx.arc() 绘制圆形,再使用 ctx.isPointInPath(x, y) 方法判断点是否在圆形内。

21.2 对于复杂的图形

如多边形、不规则图形等,可以使用数学公式进行判断。

例如,对于一个多边形,可以使用射线法来判断点是否在多边形内:

  1. 将多边形的每条边与射线相交,计算交点的数量。
  2. 如果交点的数量为奇数,则点在多边形内部;如果交点的数量为偶数,则点在多边形外部。

总结

本文涵盖了 JavaScript 基础知识的重要知识点,包括:

  • this 指向:bind 方法的永久绑定特性
  • 浮点数精度:0.1+0.2 问题的解决方案
  • ES6 新特性:Symbol、箭头函数、Promise 等
  • Promise 方法:all、allSettled、race、any 的对比
  • 数据类型判断:typeof、instanceof、Object.prototype.toString、Object.is 等多种方法
  • 字符串和数组操作:substring/substr、splice/slice 等方法的区别
  • 异步编程:脚本加载、requestAnimationFrame 等
  • 对象操作:合并、判断空对象等方法
  • Canvas API:判断点在图形内的方法

这些概念是 JavaScript 编程的基础,深入理解它们有助于编写更高效、更可靠的代码。希望本文能对您的 JavaScript 学习和复习有所帮助!

如果您对本文内容有任何疑问或补充,欢迎在评论区交流讨论~