【水平:编写简单的JavaScript】用一篇文章精通JavaScript基础

在这里插入图片描述

让我用一个生动的故事来帮你理解 JavaScript 的数据类型。

基本数据类型 vs 复杂数据类型

想象你有一个工具箱(内存空间),里面存放着各种工具(数据)。

基本数据类型(原始类型) - 简单工具

就像工具箱里的独立小工具,每个工具都是独立的:

  1. Number - 就像工具箱里的尺子

    let length = 12; // 整数 
    let price = 12.99; // 浮点数 
    
  2. String - 就像工具箱里的标签贴纸

    let toolName = "Hammer";
    let description = 'A heavy-duty hammer';
    
  3. Boolean - 就像工具箱里的开关

    let isSharp = true;
    let isBroken = false;
    
  4. Undefined - 就像工具箱里空着的格子

    let missingTool; // undefined 
    
  5. Null - 就像你故意清空的工具格

    let emptySlot = null;
    
  6. Symbol (ES6新增) - 就像工具箱里独一无二的定制工具

    let uniqueTool = Symbol('special');
    
  7. BigInt (ES2020新增) - 就像工具箱里的超大号工具

    const bigNumber = 1234567890123456789012345678901234567890n;
    

复杂数据类型(引用类型) - 工具组合套装

这些就像工具箱里的组合套装,里面包含多个工具:

  1. Object - 就像多功能工具箱

    let toolBox = {
      hammer: "Claw Hammer",
      screwdriver: "Phillips",
      quantity: 10 
    };
    
  2. Array - 就像工具挂板

    let tools = ;
    
  3. Function - 就像工具的使用说明书

    function useTool(tool) {
      return `Using ${tool}`;
    }
    
  4. Date - 就像工具箱上的日历

    let purchaseDate = new Date();
    

关键区别

  1. 存储方式

    • 基本类型:直接存储在"栈"内存中(就像把工具直接放在工作台上)
    • 引用类型:存储在"堆"内存中,栈中存储的是引用地址(就像把工具套装放在仓库里,工作台上只放一张取货单)
  2. 复制行为

    • 基本类型:复制的是值(就像复制一把锤子,得到两把独立的锤子)
    let a = 5;
    let b = a; // b得到的是5的副本 
    b = 10; // a仍然是5
    
    • 引用类型:复制的是引用(就像复制工具套装的取货单,两个取货单指向同一套工具)
    let box1 = { tool: "Hammer" };
    let box2 = box1; // box2和box1指向同一个对象
    box2.tool = "Screwdriver"; // box1.tool也变成了"Screwdriver"
    
  3. 比较方式

    • 基本类型:比较值
    5 === 5 // true
    
    • 引用类型:比较引用(内存地址)
    {} === {} // false,因为是两个不同的对象
    let obj = {};
    let obj2 = obj;
    obj === obj2 // true,因为指向同一个对象
    

类型检测方法

  1. typeof - 适合检测基本类型

    typeof 42 // "number"
    typeof "hello" // "string"
    typeof true // "boolean"
    typeof undefined // "undefined"
    typeof Symbol() // "symbol"
    typeof 123n // "bigint"
    
    // 注意这些特殊情况
    typeof null // "object" (历史遗留问题)
    typeof [] // "object"
    typeof {} // "object"
    typeof function(){} // "function"
    
  2. instanceof - 检测引用类型

    [] instanceof Array // true
    {} instanceof Object // true 
    new Date() instanceof Date // true 
    
  3. Object.prototype.toString.call() - 最准确的类型检测

    Object.prototype.toString.call(null) // ""
    Object.prototype.toString.call("
    

记住这个工具箱的比喻,JavaScript的数据类型就会变得直观多了!

作为一名全栈工程师,我来用一些生活中的故事帮你理解JavaScript中的声明与赋值概念。

1. 变量声明:给物品贴标签

想象你有一个工具箱(var),里面可以放各种工具。在JavaScript中,我们用varletconst来声明变量,就像给工具贴标签:

// 老式的工具箱 - var (函数作用域)
var hammer = "铁锤"; // 这个锤子可以在整个工具箱里使用 
 
// 新式的工具箱 - let (块级作用域)
let screwdriver = "螺丝刀"; // 这把螺丝刀只在当前隔层有效 
 
// 固定的工具 - const (常量)
const wrench = "扳手"; // 这把扳手一旦放好就不能换了 

故事时间:有一次我帮朋友装修房子,他用var声明了所有工具,结果不同房间的工具混在一起,经常找不到。后来改用let,每个房间的工具都分开管理,工作就顺利多了。

2. 变量赋值:往盒子里放东西

赋值就像往贴好标签的盒子里放东西:

let box; // 先声明一个空盒子 
box = "玩具"; // 然后往盒子里放玩具 
 
// 也可以声明时直接赋值 
let filledBox = "已经装好的礼物";

特别提醒:使用const声明的变量必须立即赋值,就像结婚戒指一旦戴上就不能随便换:

const weddingRing = "钻石戒指"; // 正确 
const emptyRing; // 错误!必须立即赋值

3. 变量提升:奇怪的魔术

JavaScript有个特殊行为叫"变量提升",就像魔术师先把东西藏起来:

console.log(magic); // 输出undefined,而不是报错
var magic = "兔子";

这相当于:

var magic; // 声明被提升到顶部
console.log(magic); // 此时magic是undefined
magic = "兔子"; // 赋值留在原地

经验分享:在早期项目中,我因为不了解变量提升,调试了很久一个奇怪的bug。现在我都用letconst来避免这个问题。

4. 数据类型:不同的储物箱

JavaScript是动态类型语言,就像多功能储物箱:

let myBox = "书本"; // 字符串 
myBox = 123; // 现在变成数字了
myBox = true; // 又变成布尔值了 

类型检测小技巧

typeof "hello"; // "string"
typeof 42; // "number"
typeof true; // "boolean"

5. 最佳实践建议

  1. 默认使用const,只有需要改变时才用let
  2. 避免使用var,除非有特殊需求
  3. 变量命名要有意义,就像给工具贴清晰的标签
  4. 声明时尽量初始化,不要留空盒子

记住这些概念,就像整理好工具箱,你的JavaScript代码会更有条理!

让我用一个电商系统的开发故事来讲解JavaScript中的运算符与表达式。记得我们开发购物车功能时,这些运算符可是天天打交道呢!

1. 算术运算符 - 购物车计算的故事

// 商品价格计算 
let price = 99.99;
let quantity = 3;
let discount = 0.2; // 20%折扣 
 
// 总价计算 
let total = price * quantity * (1 - discount); // 99.99 * 3 * 0.8 = 239.976 
 
// 四舍五入到两位小数
let finalPrice = Math.round(total * 100) / 100; // 239.98
 
console.log(`原价: ${price * quantity}元,折后价: ${finalPrice}元`);

故事:有一次客户要求购物车显示"节省金额",我们就这样计算:

let saved = price * quantity - finalPrice; // 减法运算符 
console.log(`您节省了: ${saved.toFixed(2)}元`);

2. 比较运算符 - 库存检查的故事

// 库存检查
let stock = 10;
let orderQty = 3;
 
if (orderQty > stock) {  // 大于比较 
    console.log("库存不足!");
} else if (orderQty <= 0) {  // 小于等于比较
    console.log("请输入有效数量!");
} else {
    console.log("可以购买!");
}
 
// 严格相等检查商品ID 
let productId1 = "1001";  // 字符串 
let productId2 = 1001;    // 数字 
 
if (productId1 == productId2) {  // true,值相等
    console.log("== 比较: ID匹配");
}
 
if (productId1 === productId2) {  // false,类型不同
    console.log("=== 比较: ID不匹配");
}

经验:在电商系统中,我强烈推荐使用===严格相等,避免隐式类型转换带来的bug。

3. 逻辑运算符 - 优惠券验证的故事

// 优惠券验证 
let isLoggedIn = true;
let hasCoupon = true;
let couponValid = false;
 
// 用户已登录且拥有优惠券
if (isLoggedIn && hasCoupon) {  // 逻辑与 
    console.log("可以应用优惠券");
    
    // 检查优惠券是否有效 
    couponValid = true;  // 假设验证通过
    
    if (!couponValid) {  // 逻辑非
        console.log("优惠券无效");
    }
}
 
// 新用户或首次购买 
let isNewUser = true;
let isFirstPurchase = false;
 
if (isNewUser || isFirstPurchase) {  // 逻辑或
    console.log("享受新人优惠!");
}

实战技巧:我们曾用逻辑运算符组合复杂条件:

if ((isLoggedIn && hasCoupon) || (isNewUser && !hasPurchasedBefore)) {
    // 复杂优惠逻辑
}

4. 赋值运算符 - 用户积分更新的故事

// 用户积分系统 
let points = 100;  // 初始积分
 
// 购物增加积分
points += 50;  // 等同于 points = points + 50
console.log(`当前积分: ${points}`);  // 150 
 
// 兑换商品扣除积分 
points -= 80;  // points = points - 80
console.log(`兑换后积分: ${points}`);  // 70
 
// 双倍积分活动
points *= 2;  // points = points * 2
console.log(`活动后积分: ${points}`);  // 140
 
// 特殊运算:每天登录奖励
let loginDays = 7;
points += loginDays * 5;  // 复合运算 
console.log(`连续登录奖励后积分: ${points}`);

项目经验:在开发积分系统时,我们发现+=这类运算符能让代码更简洁,也减少了出错概率。

5. 表达式实战 - 价格筛选功能

// 商品价格筛选
let minPrice = 50;
let maxPrice = 200;
let productPrice = 99;
 
// 检查价格是否在范围内 
let inRange = productPrice >= minPrice && productPrice <= maxPrice;
console.log(`商品${inRange ? '在' : '不在'}筛选范围内`);
 
// 更复杂的表达式:会员折扣+满减
let isVIP = true;
let cartTotal = 300;
let finalTotal = isVIP 
    ? cartTotal * 0.9 - (cartTotal >= 200 ? 20 : 0)  // VIP9折,满200减20 
    : cartTotal - (cartTotal >= 300 ? 30 : 0);       // 普通用户满300减30 
 
console.log(`应付金额: ${finalTotal}元`);

调试技巧:复杂表达式可以拆解调试:

let vipDiscount = isVIP ? 0.9 : 1;
let discountThreshold = isVIP ? 200 : 300;
let discountAmount = isVIP ? 20 : 30;
let discountApplicable = cartTotal >= discountThreshold;
 
// 然后组合起来 
finalTotal = cartTotal * vipDiscount - (discountApplicable ? discountAmount : 0);

记住,在真实项目中,清晰的代码比简短的代码更重要。运算符是构建逻辑的基础工具,合理使用能让你的代码既高效又易读。

作为一名全栈工程师,我来用生活中的故事帮你理解这些流程控制语句。

if 语句 - 就像天气决定穿衣

let weather = 'rainy';

if (weather === 'rainy') {
    console.log('记得带伞');
} else if (weather === 'sunny') {
    console.log('戴上太阳镜');
} else {
    console.log('随便穿吧');
}

故事:就像每天早上出门前看天气决定穿什么衣服一样,if语句让程序根据条件做不同的事情。如果下雨就带伞,晴天就戴太阳镜,其他情况就随便穿。

switch 语句 - 像餐厅点餐

let dish = '鱼香肉丝';

switch(dish) {
    case '鱼香肉丝':
        console.log('厨师开始做鱼香肉丝');
        break;
    case '宫保鸡丁':
        console.log('厨师开始做宫保鸡丁');
        break;
    default:
        console.log('抱歉,没有这道菜');
}

故事:这就像在餐厅点菜,服务员(switch)听到你点的菜名(case),就会通知厨房做对应的菜。如果菜单上没有(default),就会告诉你没有这道菜。break就像服务员确认订单后离开,不再继续问其他菜。

for 循环 - 像数楼梯台阶

for(let i = 1; i <= 10; i++) {
    console.log(`这是第 ${i} 级台阶`);
}

故事:想象你爬楼梯时数台阶。从第1级(i=1)开始,每上一级(i++)就数一下,直到第10级(i<=10)为止。for循环就是这样有明确的开始、结束和步进。

while 循环 - 像等公交车

let busArrived = false;
let waitingTime = 0;

while(!busArrived) {
    waitingTime++;
    console.log(`已等待 ${waitingTime} 分钟`);
    
    if(waitingTime >= 15) {
        busArrived = true;
        console.log('公交车终于来了!');
    }
}

故事:就像在公交站等车,只要车没来(!busArrived),你就一直等(循环)。每过1分钟(waitingTime++)你都看一下时间。等了15分钟后,车终于来了,你就不再等了(循环结束)。

do-while 循环 - 像试吃活动

let isDelicious;
let tryCount = 0;

do {
    tryCount++;
    console.log(`这是第 ${tryCount} 次试吃`);
    isDelicious = confirm('好吃吗?');
} while(!isDelicious && tryCount < 3);

故事:超市的试吃活动总会让你至少尝一次(do),然后问你是否好吃。如果不好吃(!isDelicious)且尝试次数少于3次(tryCount < 3),就会让你再尝一次(while)。这和while循环的区别在于,它至少会执行一次。

实际开发中的经验

  1. if vs switch:条件少用if,多个固定值判断用switch更清晰
  2. 循环选择:知道循环次数用for,不确定用while
  3. 避免无限循环:确保循环条件最终会变为false
  4. 性能考虑:大数据量时,循环内的操作要尽量高效

记住这些流程控制语句就像生活中的各种决策和重复活动,理解了这个本质,编程就会变得很自然。

让我用一个生动的故事来为你讲解DOM的节点层次和类型概念。

故事:图书馆的图书管理系统

想象你正在设计一个图书馆的管理系统,DOM就像这个图书馆的组织结构:

1. 文档节点(Document) - 图书馆大楼

  • 这是整个DOM树的根节点,就像图书馆大楼本身
  • 所有其他节点都包含在这个"大楼"内
  • 通过document对象访问
// 就像进入图书馆大门 
console.log(document); // 整个文档对象

2. 元素节点(Element) - 书架和分类标签

  • 对应HTML标签,如<div>, <p>, <ul>
  • 就像图书馆里的书架和分类标签
// 获取某个书架(元素)
const shelf = document.getElementById('history-section');

3. 文本节点(Text) - 书籍内容

  • 包含在元素节点内的文本内容
  • 就像书架上书籍里的实际文字内容
// 获取书籍内容(文本节点)
const bookContent = shelf.firstChild.nodeValue;

4. 属性节点(Attr) - 书籍的附加信息

  • 元素的属性,如id, class, href
  • 就像书籍的ISBN号、作者标签等附加信息
// 获取书籍的ISBN(属性)
const isbn = shelf.getAttribute('data-isbn');

5. 注释节点(Comment) - 管理员的便签

  • HTML中的注释内容
  • 就像管理员留在书架上的便签说明
// 获取注释节点(如果有的话)
const comments = document.querySelectorAll('*');
comments.forEach(node => {
  if(node.nodeType === Node.COMMENT_NODE) {
    console.log('管理员便签:', node.nodeValue);
  }
});

节点层次关系

继续用图书馆的比喻:

  1. 父节点(Parent Node)

    - 上级书架

    • 直接包含当前节点的节点
    • <ul><li>的父节点
  2. 子节点(Child Node)

    - 下级书籍

    • 被当前节点直接包含的节点
    • <li><ul>的子节点
  3. 兄弟节点(Sibling Node)

    - 同一层的书籍

    • 共享同一个父节点的节点
    • 如两个相邻的<li>是兄弟节点
// 实际DOM操作示例 
const library = document.getElementById('library');
 
// 获取所有历史类书籍(子节点)
const historyBooks = library.children;
 
// 获取历史类区域旁边的文学区域(兄弟节点)
const literatureSection = historyBooks.nextElementSibling;
 
// 获取整个图书馆大楼(父节点)
const building = library.parentNode;

节点类型判断

在DOM中,每个节点都有nodeType属性:

// 节点类型常量 
Node.ELEMENT_NODE (1)       // 元素节点
Node.ATTRIBUTE_NODE (2)     // 属性节点
Node.TEXT_NODE (3)          // 文本节点
Node.COMMENT_NODE (8)       // 注释节点 
Node.DOCUMENT_NODE (9)      // 文档节点
Node.DOCUMENT_TYPE_NODE (10) // 文档类型节点

实际应用:

function describeNode(node) {
  switch(node.nodeType) {
    case Node.ELEMENT_NODE:
      return `书架: ${node.tagName}`;
    case Node.TEXT_NODE:
      return `书籍内容: ${node.nodeValue.trim() || ''}`;
    case Node.COMMENT_NODE:
      return `管理员便签: ${node.nodeValue}`;
    default:
      return `其他图书馆物品: ${node.nodeName}`;
  }
}

通过这个图书馆的比喻,你应该能更直观地理解DOM的节点层次和类型了。记住,就像管理图书馆一样,操作DOM时要有条理,知道每个"物品"的位置和属性,才能高效地"管理"你的网页内容。

作为一名全栈工程师,我来用生活中的故事帮你理解DOM操作。想象DOM就像是一个家装设计师在改造一间房子:

1. 获取元素 - 找到要改造的部分

// 就像设计师先找到要改造的墙面 
const wall = document.getElementById('main-wall'); // 通过ID找 
const windows = document.querySelectorAll('.window'); // 通过类名找多个 
const door = document.querySelector('#front-door'); // 通过选择器找单个

故事:记得我刚工作时,有个项目需要批量修改商品价格。就像设计师要找到所有需要更换的灯具,我用querySelectorAll找到了所有价格元素,然后统一调整,效率提高了10倍。

2. 修改属性 - 改变元素特性

// 就像改变墙面的颜色和材质
wall.style.backgroundColor = 'skyblue';
door.setAttribute('data-material', 'wood');
 
// 修改图片就像更换墙上的画 
const painting = document.querySelector('.painting');
painting.src = 'new-artwork.jpg';

经验:曾有个客户要求夜间模式切换,就像给房间换灯光。我们通过批量修改元素的classList来实现主题切换:

document.body.classList.toggle('dark-mode');

3. 添加/删除元素 - 改变房间布局

// 添加新家具就像创建新元素 
const newSofa = document.createElement('div');
newSofa.className = 'furniture sofa';
document.querySelector('.living-room').appendChild(newSofa);
 
// 移除旧家具
const oldTable = document.querySelector('.old-table');
oldTable.parentNode.removeChild(oldTable);

项目案例:电商网站的商品列表就像房间里的家具。当用户筛选时,我们需要:

// 清空现有商品 
const productList = document.getElementById('products');
productList.innerHTML = ''; // 快速清空 
 
// 添加筛选后的商品 
filteredProducts.forEach(product => {
    const item = document.createElement('div');
    item.innerHTML = `<h3>${product.name}</h3><p>${product.price}</p>`;
    productList.appendChild(item);
});

4. 事件处理 - 让房间变得智能

// 点击门铃就像添加点击事件
door.addEventListener('click', () => {
    console.log('客人来了!');
    door.style.backgroundColor = 'red'; // 门铃亮起
});
 
// 表单提交就像房间的智能控制系统
document.querySelector('.thermostat-form').addEventListener('submit', (e) => {
    e.preventDefault(); // 阻止默认提交行为 
    const temperature = document.getElementById('temp-input').value;
    adjustRoomTemperature(temperature); // 自定义温度调节函数
});

实战技巧:事件委托就像在门口安装总控制器:

// 只需监听父元素,处理所有子元素事件 
document.getElementById('light-switches').addEventListener('click', (e) => {
    if(e.target.classList.contains('switch')) {
        toggleLight(e.target.dataset.room); // 根据data-room属性控制不同房间灯光 
    }
});

性能优化小故事

曾经有个项目DOM操作特别慢,就像装修时工人一个个搬家具。后来我学会了:

  1. 文档片段 - 先把家具在仓库组装好再搬入
const fragment = document.createDocumentFragment();
items.forEach(item => {
    const el = createItemElement(item); // 创建元素的函数
    fragment.appendChild(el);
});
container.appendChild(fragment);
  1. 批量修改 - 不要一边测量一边刷墙
// 不好:多次重绘
elements.forEach(el => el.style.display = 'none');
 
// 好:一次性修改 
container.style.display = 'none';
elements.forEach(el => el.classList.add('hidden'));
container.style.display = 'block'; 

记住,DOM操作就像装修房子,要规划好再动手,避
免不必要的"拆了又装"!

思维导图

在这里插入图片描述

下期我们说:JavaScript的高级部分——异步编程!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java自学之旅

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值