尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

JavaScript变量与数据类型详解 - JavaScript学习系列文章

JavaScript变量与数据类型详解 - JavaScript学习系列文章 一、变量声明在 JavaScript 中, 我们主要有三种声明变量的方式:var、let和const.它们看起来差不多, 但实际上差别可大了!1) 老派风的varvar是 JavaScript 最早的变量声明方式, 它有一些独特的行为:var name 小明;console.log(name);// 输出: 小明// var 允许重复声明var name 小红;console.log(name);// 输出: 小红// var 有变量提升的特性console.log(age);// 输出: undefined (不会报错!)var age 18;问题:可以重复声明容易导致变量被意外覆盖存在变量提升(hoisting), 代码执行前会把声明提到作用域顶部只有函数作用域没有块级作用域2) 现代派的letES6 引入了let,解决了var的很多问题:let name 小明;console.log(name);// 输出: 小明// let 不允许重复声明let name 小红;// 报错: Identifier name has already been declared// let 也有提升, 但不允许在声明前使用(暂时性死区)console.log(age);// 报错: Cannot access age before initializationlet age 18;// let 有块级作用域if (true) {let food 苹果;console.log(food);// 输出: 苹果}console.log(food);// 报错: food is not defined3) 常量constconst用于声明常量, 声明后不能重新赋值:constPI 3.14159;console.log(PI);// 输出: 3.14159PI 3.14;// 报错: Assignment to constant variable// 但注意! 对于对象和数组, const 只保证引用不变const person {name:小明 };person.name 小红;// 这是允许的!console.log(person);// 输出: { name: 小红 }person {name:小刚 };// 报错: Assignment to constant variable4) 三者之间的对比特性varletconst作用域函数作用域块级作用域块级作用域重复声明允许不允许不允许变量提升有有(但不可用)有(但不可用)可重新赋值可以可以不可以必须初始化不需要不需要需要建议:默认使用const需要重新赋值的变量用let避免使用var (除非你需要兼容非常老的浏览器)二、JavaScript的数据类型1) 基本类型基本类型有 7 种:number- 数字string- 字符串boolean- 布尔值null- 空值undefined- 未定义symbol- 符号 (ES6新增)bigint- 大整数 (ES2020新增)特点:存储在栈内存中按值访问不可变(immutable)比较时比较的是值示例:let a 10;let b a;// b 是 a 的值的一个副本a 20;console.log(a);// 20console.log(b);// 10let str hello;str[0] H;// 字符串是不可变的这行代码不会报错但也不起作用console.log(str);// 输出: hello2) 引用类型引用类型主要是:Object (包括数组、函数、日期、正则表达式等)特点:存储在堆内存中按引用访问可变(mutable)比较时比较的是引用(内存地址)示例:let obj1 {name:小明 };let obj2 obj1;// obj2和 obj1指向同一个对象obj1.name 小红;console.log(obj2.name);// 输出:小红 (因为两者引用同一个对象)let arr1 [1,2,3];let arr2 arr1;arr1.push(4);console.log(arr2);// 输出:[1, 2, 3, 4]3) 基本类型与引用类型的比较// 基本类型比较let num1 5;let num2 5;console.log(num1 num2);// true (值相同)// 引用类型比较let obj1 {name:小明 };let obj2 {name:小明 };console.log(obj1 obj2);// false (虽然内容相同, 但引用不同)let obj3 obj1;console.log(obj1 obj3);// true (引用相同)4) 特殊的null和undefinednull和undefined都表示无, 但有细微差别:undefined表示变量已声明但未赋值null表示变量有值, 但这个值是空let a;console.log(a);//undefined (声明但未赋值)let b null;console.log(b);//null (明确赋值为空)// 有趣的现象console.log(typeofnull);//object (这是历史遗留bug)console.log(typeofundefined);//undefined三、类型检测1) typeof运算符typeof是最常用的类型检测方法:typeof42;// numbertypeofhello;// stringtypeoftrue;// booleantypeofundefined;// undefinedtypeofSymbol();// symboltypeof123n;// bigint// 两个特殊情况typeofnull;// object (历史遗留问题)typeoffunction() {};// function问题:不能区分数组和普通对象(都返回 object)对 null 返回 object2) instanceof运算符instanceof用于检测对象是否是某个构造函数的实例:let arr [1,2,3];arrinstanceofArray;//truearrinstanceofObject;//true (因为数组也是对象)let date newDate();dateinstanceofDate;//truedateinstanceofObject;//true// 基本类型不能用 instanceof检测42instanceofNumber;//false3) Object.prototype.toString.call()这是最准确的类型检测方法:Object.prototype.toString.call(42);//[object Number]Object.prototype.toString.call(hello);//[object String]Object.prototype.toString.call(true);//[object Boolean]Object.prototype.toString.call(null);//[object Null]Object.prototype.toString.call(undefined);//[object Undefined]Object.prototype.toString.call([]);//[object Array]Object.prototype.toString.call({});//[object Object]Object.prototype.toString.call(newDate());//[object Date]4) Array.isArray()专门用于检测数组:Array.isArray([]);//trueArray.isArray({});//false5) 最佳实践检测基本类型(除了null)用typeof检测数组用Array.isArray()检测null用 null需要精确检测对象类型时用Object.prototype.toString.call()知道对象的具体构造函数时可以用instanceof四、类型转换JavaScript 是弱类型语言, 会自动进行类型转换. 这既是便利也是坑!1) 显示类型转换字符串转换:String(123);//123String(true);//trueString(null);//nullString(undefined);//undefined//等价于123.toString();//123true.toString();//true//注意:null和 undefined没有toString方法数字转换:Number(123);//123Number(123abc);//NaN(Not a Number)Number(true);//1Number(false);//0Number(null);//0Number(undefined);//NaN//其他方法parseInt(123px);//123parseFloat(3.14.15);//3.14123;//123 (一元加号运算符)布尔值转换:Boolean(1);// trueBoolean(0);//falseBoolean();//falseBoolean(hello);//trueBoolean(null);//falseBoolean(undefined);//falseBoolean({});//trueBoolean([]);//true//等价于!!1;//true(双感叹号技巧)!!0;//false2) 隐式类型转换JavaScript 在某些情况下会自动进行类型转换:字符串拼接今年是 2023;//今年是2023 (数字转为字符串)1 2;//12数学运算10 -5;//5 (字符串转为数字)10 *2;//2010 /2;//5abc -1;// NaN//注意加法的特殊性1 2 3;//33 (从左到右计算)1 2 3;//123布尔值if (hello) {//hello被转为 trueconsole.log(执行);}//常见的假值(falsy values)://false, 0, , null, undefined, NaN//其他所有值都是真值(true)和的区别1 1; //true(类型转换后比较)1 1; //false(严格比较不转换类型)0 false; //true0 false; //falsenull undefined; //truenull undefined; //false总是应该使用和!避免隐式转换带来的意外行为.五、深拷贝与浅拷贝1) 浅拷贝 (只拷贝第一层属性)let person {name:小明,hobbies: [篮球,音乐] };//方法1:Object.assignlet copy1 Object.assign({}, person);//方法2:展开运算符let copy2 { ...person };//修改拷贝后的对象copy1.name 小红;copy1.hobbies.push(游泳);console.log(person.name);//小明(未受影响)console.log(person.hobbies);//[篮球, 音乐, 游泳](受影响)2) 深拷贝 (完全拷贝所有层级)//方法1: JSON.parse(JSON.stringify()) (最简单但有局限)let deepCopy1 JSON.parse(JSON.stringify(person));//方法2: 使用工具库如lodash的_.cloneDeep//let deepCopy2 _.cloneDeep(person);//方法3: 手动实现递归拷贝functiondeepClone(obj) {if (obj null ||typeof obj !object)return obj;let clone Array.isArray(obj) ? [] : {};for (let keyin obj) {if (obj.hasOwnProperty(key)) {clone[key] deepClone(obj[key]);}}return clone;}let deepCopy3 deepClone(person);六、ES6新特性1) 模板字符串const name 小明;const age 18;console.log(我叫${name}, 今年${age}岁);//我叫小明, 今年18岁2) 解构赋值//对象解构const person {name:小明,age:18 };const { name, age } person;//数组解构const arr [1,2,3];const [first, second] arr;3) 新的原始类型Symbol:const sym1 Symbol(唯一标识);const sym2 Symbol(唯一标识);console.log(sym1 sym2);//false(每个Symbol都是唯一的)//常用作对象的唯一键const obj {[sym1]:这是Symbol作为键的值};BigInt:const bigNum 1234567890123456789012345678901234567890n;console.log(bigNum 1n);//可以表示和操作超大整数
返回列表