目录
1.splice在js中为什么可以当作数组的删除函数,它的英语本意是粘贴呀?
1. 英语原意 vs JavaScript 的功能
2. 为什么用 "splice" 表示删除?
3. 对比 slice 和 splice
4. 为什么感觉「别扭」?
5. 设计哲学
总结
2.为什么js的数组还可以被当作map集合那样使用也就是数组的[]中的参数可以是-1 , aaa 中文等等?
1. JavaScript 数组的本质是「对象」
2. 为什么允许非数字索引?
3. 非数字索引的陷阱
4. 数组 vs Map
5. 为什么允许 arr[-1]?
6. 总结:数组的正确使用姿势
为什么设计如此「怪异」?
最佳实践
3.为什么我觉得JavaScript的语法很奇怪,在我学习了Java之后,很多地方的定义都出乎我的意料?
1. 语言定位与设计目标
2. 核心语法差异对比
变量与类型系统
函数与作用域
面向对象编程(OOP)
3. 让你「出乎意料」的常见陷阱
动态类型与隐式转换
作用域与变量提升
this 的动态绑定
4. 为什么 JavaScript 要这样设计?
5. 如何适应 JavaScript 的「怪」?
总结
4.JavaScript 的模板字符串(Template Literals)和 jQuery 中类似 $('...') 的选择器语法看似有相似的符号 ${} 和 $ 二者之间的异同?
1、核心区别对比表
2、语法细节解析
1. JavaScript 模板字符串
2. jQuery 选择器语法
3、可能混淆的场景
1. 动态生成选择器字符串
2. 其他模板引擎的误导
4、总结:二者毫无关联,但可协作
问题1:字符串加减以及+-一元运算符
1. console.log(1 + "2" + "2");
2. console.log(1 + +"2" + "2");(重点分析)
3. console.log("A" - "B" + "2");
4. console.log("A" - "B" + 2);
总结:JavaScript 的类型转换规则
重点问题再解析(第二行 1 + +"2" + "2")
练习巩固
问题2:Java和JavaScript的短路原则语法对比
JavaScript 代码解析
JavaScript 逻辑运算符规则
Java 的对比分析
Java 代码尝试
Java 逻辑运算符规则
Java 合法替代写法
问题3:JavaScript中Object.is()方法和严格相等运算符===的区别
JavaScript 知识点详解
1. Object.is() 与 === 的区别
2. 特殊值的处理逻辑
这是一个非常有意思的观察!你的困惑其实源于 JavaScript 中 splice
方法的设计逻辑和英语原意的微妙关系。我来为你拆解这个「别扭感」的来源和背后的逻辑:
英语中的 "splice"
本意是「拼接」(比如拼接绳子、拼接胶片),指将两个部分连接在一起。例如:
电影剪辑中,"splice film" 表示将两段胶片粘合。
电缆工程中,"splice wires" 表示将两根电线连接。
JavaScript 中的 splice
它同时具备 删除、添加、替换 数组元素的能力,本质上是在「修改原数组的某一段,并用新内容拼接上去」。例如:
const arr = [1, 2, 3, 4, 5]; arr.splice(1, 2, 'a', 'b'); // 从索引1开始,删除2个元素,添加'a'和'b' console.log(arr); // [1, 'a', 'b', 4, 5]
这里的行为就像将原数组「切断」一部分,再「拼接」新的内容。
历史原因:splice
方法的设计灵感可能来源于早期编程语言(如 Lisp)中对列表的操作术语,其中「剪切并替换」被抽象为一种「拼接」行为。
功能本质:splice
不单纯是删除,而是「在数组中某个位置切开一个口子」,你可以选择:
仅删除元素(不添加新内容)
删除并添加新元素
仅添加新元素(删除数量设为0)
// 只删除 arr.splice(1, 2); // 删除索引1开始的2个元素 // 只添加(删除0个) arr.splice(1, 0, 'x'); // 在索引1插入'x'
slice
和 splice
slice
:英语原意是「切片」,对应它的功能——纯函数,返回数组的一个片段,不修改原数组。
const sub = arr.slice(1, 3); // 原数组不变
splice
:英语原意是「拼接」,对应它的功能——直接修改原数组,像手术刀一样「切开并重新拼接」。
如果你只使用 splice
的删除功能(比如 arr.splice(1, 1)
),会觉得它和「拼接」无关。
但它的完整功能更符合「拼接」:删除旧内容,插入新内容,像修改胶片一样「剪掉一段,接上一段」。
JavaScript 的数组方法命名常常借用现实世界的隐喻:
push
/pop
:像栈一样「推入」和「弹出」。
shift
/unshift
:像队列的「移动」。
splice
:用「拼接」隐喻数组的局部修改。
splice
的「别扭感」源于你只关注了它的删除功能,而它的设计初衷是更通用的「剪切并拼接」。如果要找一个纯删除的方法,可以用 filter
,但 splice
的独特之处在于它能直接修改原数组并精确控制位置。下次使用时,可以想象自己在「剪接数组」,这样名字就合理多了! ✂️➔
这是一个非常关键的问题!JavaScript 的数组(Array
)和其他语言的数组设计理念完全不同,它的行为更像一个「混合体」。你观察到的现象(数组可以像 Map 一样使用非数字索引)源于 JavaScript 的历史设计和动态语言的特性。让我们一步步拆解这个问题的本质:
底层逻辑:JavaScript 的数组实际上是披着数组外衣的特殊对象。当你创建一个数组时:
const arr = [1, 2, 3];
它在底层等价于:
const arr = { 0: 1, 1: 2, 2: 3, length: 3, __proto__: Array.prototype // 继承数组方法 };
数字索引只是属性名:arr[0]
本质上是访问对象的属性 0
,而 JavaScript 允许对象的属性名是字符串或 Symbol,包括数字字符串(如 "0"
、"-1"
)或任意字符串(如 "aaa"
)。
动态语言的特性:JavaScript 是弱类型、动态的,允许运行时动态扩展对象的属性。
数组的宽容性:为了灵活性,数组不严格限制你只能使用数字索引。你可以为数组添加任意属性:
const arr = [1, 2, 3]; arr[-1] = "我是负数索引"; arr["aaa"] = "我是字符串索引"; arr[{ id: 123 }] = "甚至对象作为键(会被转成字符串)";
但这些属性不属于「数组元素」:
使用 arr.length
不会统计这些非数字键的属性。
数组方法(如 map
、forEach
、filter
)会忽略这些属性。
看似数组,实为对象:当你使用非数字索引时,数组退化为普通对象的行为。
const arr = [1, 2, 3]; arr["foo"] = "bar"; console.log(arr.length); // 3(非数字键不影响长度) console.log(arr); // [1, 2, 3, foo: 'bar']
性能问题:V8 引擎对「纯数字索引的数组」有优化(存储为连续内存),但如果添加了非数字属性,数组会被降级为「字典模式」,失去性能优势。
设计目的不同:
数组:有序集合,专注于数字索引的快速访问。
Map:键值对集合,支持任意类型的键(包括对象、NaN),且键值对顺序可追踪。
何时用 Map:
// 需要任意类型键时,用 Map const map = new Map(); map.set(-1, "负数索引"); map.set("aaa", "字符串索引"); map.set({ id: 1 }, "对象作为键");
arr[-1]
?语法糖的代价:JavaScript 允许 arr[-1]
,但它会被转换为字符串 "-1"
,访问的是数组对象的 "-1"
属性,而不是倒数第一个元素!
获取倒数元素的正解:
// 正确方式:用 arr[arr.length - 1] const lastElement = arr[arr.length - 1];
场景 | 正确工具 | 错误工具 |
---|---|---|
有序数字索引集合 | 数组 (Array ) |
对象/Map |
任意类型键的键值对 | Map | 数组/普通对象 |
需要负数/字符串索引 | Map | 数组(违反设计) |
历史包袱:JavaScript 早期设计受 Java 影响,但为了简化又采用了动态特性。
灵活性优先:允许开发者将数组当作「超级对象」使用,但也带来了误用风险。
严格区分数据用途:
纯数字索引集合 → 数组。
键值对 → Map
或普通对象。
避免数组滥用:
// ❌ 错误:用数组存储键值对 const badArray = []; badArray["user"] = "John"; badArray["age"] = 30; // ✅ 正确:用对象或 Map const goodObj = { user: "John", age: 30 }; const goodMap = new Map([["user", "John"], ["age", 30]]);
防御性编程:
// 检查一个属性是否是真正的数组元素 function isArrayElement(arr, index) { return index >= 0 && index < arr.length; }
JavaScript 的数组设计是一种「能力越大,责任越大」的哲学——它给你极大的灵活性,但也需要开发者自律地使用。
你在学习 JavaScript 后感到「语法奇怪」,尤其是从 Java 转向 JavaScript 时,这种反差感非常普遍。这种「怪异感」本质上源于 JavaScript 和 Java 在语言设计哲学、类型系统、运行时行为等方面的根本性差异。以下是具体原因和关键对比:
Java:
强类型、静态、面向对象,设计初衷是「一次编写,到处运行」的企业级应用开发。
严格性优先:编译器强制类型检查,要求显式声明类、接口和类型。
JavaScript:
动态、弱类型、多范式,设计初衷是「为网页添加简单的交互」。
灵活性优先:允许运行时动态修改对象、类型,甚至代码结构。
特性 | Java | JavaScript |
---|---|---|
变量声明 | 必须显式声明类型 (int x = 5; ) |
var /let /const 无需类型注解 |
类型检查 | 编译时强类型检查(类型不匹配报错) | 运行时弱类型(隐式类型转换) |
默认值 | 基本类型有默认值(如 int 为 0) |
变量未赋值时为 undefined |
类型系统 | 原生支持基本类型和对象类型 | 只有 number 、string 、boolean 等动态类型 |
示例:类型容忍度
// JavaScript 允许以下操作 let x = "5" + 2; // "52" (字符串拼接) let y = "5" - 2; // 3 (隐式转换为数字)
特性 | Java | JavaScript |
---|---|---|
函数定义 | 必须属于某个类(方法) | 函数是一等公民,可独立存在 |
作用域 | 块级作用域({} 内) |
早期只有函数作用域,ES6 引入 let /const 块级作用域 |
闭包 | 不存在(Lambda 有限支持) | 天然支持闭包 |
示例:函数作为参数传递
// JavaScript 中函数可以像变量一样传递 function greet(name) { return `Hello, ${name}!`; } function wrapper(func, value) { return func(value); } wrapper(greet, "Alice"); // "Hello, Alice!"
特性 | Java | JavaScript |
---|---|---|
继承机制 | 基于类的继承 (extends ) |
基于原型的继承(无类,只有对象和原型链) |
this 关键字 |
指向当前实例对象 | 动态绑定,取决于调用方式 |
封装性 | private /protected 修饰符 |
无原生私有属性(ES2022+ 支持 # 私有字段) |
示例:原型链继承
function Animal(name) { this.name = name; } Animal.prototype.speak = function() { console.log(`${this.name} makes a noise`); }; function Dog(name) { Animal.call(this, name); } Dog.prototype = Object.create(Animal.prototype); Dog.prototype.constructor = Dog; const dog = new Dog("Rex"); dog.speak(); // "Rex makes a noise"
console.log(1 + "1"); // "11" (不是 2) console.log([] == 0); // true ([] 转换为 "" → 0) console.log(null == undefined); // true (但 null !== undefined)
console.log(x); // undefined (不是报错) var x = 5; // let x = 5; 会报错(暂时性死区)
this
的动态绑定const obj = { name: "Alice", logName: function() { console.log(this.name); } }; const log = obj.logName; log(); // undefined(严格模式下 this 是 undefined)
历史原因:JavaScript 在 10 天内被设计出来,最初目标是为网页添加简单交互,而非复杂应用。
动态语言的优势:允许快速原型开发、灵活修改对象,适合不确定需求变化的场景(如早期网页开发)。
函数式编程基因:高阶函数、闭包等特性使其适合异步编程和事件驱动模型。
理解核心理念:
接受「一切皆对象」(包括函数)和「原型链」的概念。
用 ===
代替 ==
避免隐式转换。
使用 let
/const
和严格模式 ("use strict"
) 减少意外行为。
工具辅助:
使用 TypeScript 添加静态类型检查。
利用 ESLint 捕捉潜在错误。
学习现代 JavaScript:
ES6+ 的 class
语法糖、模块化、Promise 等特性让代码更接近传统 OOP 语言。
Java 和 JavaScript 的差异类似于「严谨的建筑图纸」与「灵活的手工雕塑」。Java 强调结构性和安全性,JavaScript 则追求表达力和适应性。这种设计差异导致语法和行为的「怪异感」,但也正是 JavaScript 能在浏览器、服务端(Node.js)、移动端(React Native)等多领域大放异彩的原因。理解这些差异后,你会逐渐欣赏 JavaScript 的独特哲学!
$('...')
的选择器语法看似有相似的符号 ${}
和 $ 二者之间的异同?
特性 | JavaScript 模板字符串 | jQuery 选择器语法 |
---|---|---|
符号作用 | 字符串插值(变量替换) | DOM 元素选择器 |
符号位置 | 反引号字符串内使用 ${expression} |
$() 包裹 CSS 选择器字符串 |
底层原理 | ES6 语法特性,由 JavaScript 引擎解析 | jQuery 库提供的函数,解析字符串查找 DOM |
使用场景 | 动态拼接字符串、多行文本 | 快速选取或操作 HTML 元素 |
依赖关系 | 原生 JavaScript 支持(无需库) | 需引入 jQuery 库 |
语法:使用 反引号(`
)包裹字符串,通过 ${}
嵌入变量或表达式。
用途:动态生成字符串内容,替代传统字符串拼接。
const name = "Alice"; const age = 25; // 传统拼接 const oldStr = "Name: " + name + ", Age: " + age; // 模板字符串 const newStr = `Name: ${name}, Age: ${age}`;
高级用法:支持多行字符串和嵌套表达式。
const html = ``;${name.toUpperCase()}
年龄:${age > 18 ? "成年" : "未成年"}
语法:通过 $()
函数传入 CSS 选择器字符串,返回匹配的 DOM 元素集合。
用途:快速操作 DOM 元素,与模板字符串无直接关联。
// 选择 id 为 "header" 的元素 const $header = $('#header'); // 选择 class 为 "item" 的所有元素 const $items = $('.item'); // 动态选择器(需结合模板字符串) const dynamicId = "main"; const $dynamicElement = $(`#${dynamicId}`); // 此处模板字符串生成选择器字符串
若在 jQuery 的 $()
中使用模板字符串生成动态选择器,二者会结合使用,但本质是独立分工:
const id = "myElement"; // 使用模板字符串生成选择器字符串,再交给 jQuery 解析 const $element = $(`#${id}`);
关键点:
模板字符串仅负责生成字符串(如 "#myElement"
)
jQuery 的 $()
函数负责解析该字符串并选择 DOM
某些模板引擎(如 Vue、Angular)或旧版库可能使用类似 ${}
的插值语法,但这与 JavaScript 模板字符串无直接关系。例如:
Vue 模板:使用 {{ value }}
插值(非 ${}
)。
jQuery 插件:个别插件可能自定义类似语法,但非 jQuery 核心功能。
模板字符串是 JavaScript 语言层面的字符串插值工具。
jQuery 选择器是库提供的 DOM 操作接口。
协作场景:用模板字符串生成选择器字符串,再传递给 $()
函数。
重点注意:
勿混淆语法:${}
仅在反引号字符串中有效,$()
是 jQuery 的函数调用。
勿误解功能:模板字符串不操作 DOM,jQuery 不解析 ${}
插值(除非显式生成字符串后传递)。
理解二者的独立性和协作方式,可避免代码中的误用和逻辑混乱!
console.log(1 + "2" + "2");
输出: "122"
解析:
第一个 +
操作:1 + "2"
数字 1
与字符串 "2"
相加,触发字符串拼接,结果为 "12"
(字符串)。
第二个 +
操作:"12" + "2"
两个字符串直接拼接,结果为 "122"
。
console.log(1 + +"2" + "2");
(重点分析)关键:Java 严格禁止对非数值类型应用一元运算符。
输出: "32"
解析:
一元加号 +"2"
:
+"2"
将字符串 "2"
转换为数字 2
。
第一个 +
操作:1 + 2
数字相加,结果为 3
(数字)。
第二个 +
操作:3 + "2"
数字 3
与字符串 "2"
相加,触发字符串拼接,结果为 "32"
(字符串)。
关键点:
一元加号 +
的优先级高于加法 +
,所以 +"2"
会先执行。
表达式等价于 (1 + (+"2")) + "2"
。
练习:console.log(1 + -"2" + "2")
逐步解析 console.log(1 + -"2" + "2")
的输出结果:
处理一元负号运算符 -"2"
-"2"
将字符串 "2"
转换为数字 2
,然后取负数 → -2
。
执行数值加法 1 + (-2)
1
(数字) + -2
(数字) → -1
(数字)。
执行字符串拼接 -1 + "2"
-1
(数字)与 "2"
(字符串)相加时,数字转换为字符串 → "-1"
+ "2"
→ "-12"
(字符串)。
console.log("A" - "B" + "2");
输出: "NaN2"
解析:
减法操作 "A" - "B"
:
字符串 "A"
和 "B"
无法转换为有效数字,结果为 NaN
(Not a Number)。
加法操作 NaN + "2"
:
NaN
与字符串 "2"
相加,触发字符串拼接,结果为 "NaN2"
(字符串)。
console.log("A" - "B" + 2);
输出: NaN
解析:
减法操作 "A" - "B"
:
同上,结果为 NaN
。
加法操作 NaN + 2
:
NaN
与任何数字相加,结果仍为 NaN
。
操作符 | 行为 |
---|---|
+ |
若一侧为字符串,优先拼接字符串;否则进行数字加法。 |
- |
始终尝试将两侧转换为数字,无法转换时结果为 NaN 。 |
一元 + |
将操作数强制转换为数字(例如 +"2" → 2 ,+"A" → NaN )。 |
1 + +"2" + "2"
)执行顺序:
javascript
复制
下载
1 + (+"2") + "2" // 分解步骤 → 1 + 2 + "2" // 一元加号将 "2" 转为数字 2 → 3 + "2" // 数字 1 + 2 = 3 → "32" // 数字 3 与字符串 "2" 拼接
为什么不是 1 + "2" + "2" = "122"
?
一元加号 +
改变了优先级和类型转换逻辑,导致 +"2"
被优先转换为数字。
如果修改第二行为 1 + +"2" + +"2"
,结果会是什么?
答案: 5
(数字),因为两次一元加号将字符串转为数字,最终执行 1 + 2 + 2
。
console.log(0 && 1, 0 || 1, 1 && 3, 1 || 3); // 输出:0 1 3 1
&&
(逻辑与):
行为:若左侧为真值(truthy),返回右侧操作数;否则返回左侧操作数。
短路逻辑:左侧为假值时,右侧不执行。
||
(逻辑或):
行为:若左侧为真值,返回左侧操作数;否则返回右侧操作数。
短路逻辑:左侧为真值时,右侧不执行。
Java 的逻辑运算符与 JavaScript 存在本质区别,以下是相同表达式在 Java 中的行为和结果:
// 以下代码均无法编译! System.out.println(0 && 1); // 错误:操作数类型不兼容 System.out.println(0 || 1); // 错误:操作数类型不兼容 System.out.println(1 && 3); // 错误:操作数类型不兼容 System.out.println(1 || 3); // 错误:操作数类型不兼容
&&
和 ||
:
操作数类型:必须是布尔值(boolean
),不接受其他类型(如 int
)。
返回值:布尔值 true
或 false
。
短路逻辑:与 JavaScript 一致,但仅适用于布尔表达式。
Java 的类型安全:
整数(如 0
、1
)不能直接作为逻辑运算的操作数,必须显式转换为布尔值(例如通过比较操作)。
System.out.println((0 != 0) && (1 != 0)); // false && true → false System.out.println((0 != 0) || (1 != 0)); // false || true → true System.out.println((1 != 0) && (3 != 0)); // true && true → true System.out.println((1 != 0) || (3 != 0)); // true || true → true
下列表达式中,返回值为true的是()
①Object.is(NaN,NaN)
②Object.is(+0,-0)
③NaN === NaN
④+0 === -0
Object.is()
与 ===
的区别场景 | Object.is() |
=== (严格相等) |
---|---|---|
NaN 比较 | NaN === NaN → true |
NaN === NaN → false |
+0 与 -0 | Object.is(+0, -0) → false |
+0 === -0 → true |
NaN 的独特性:
NaN
是唯一不等于自身的值(NaN !== NaN
)。
Object.is()
专门修复此问题,认为 NaN
等于自身。
+0 与 -0 的物理差异:
1/+0 → Infinity
,1/-0 → -Infinity
。
===
忽略符号差异,而 Object.is()
严格区分。