【前端开发入门】JavaScript快速入门--数据操作
目录
- 引言
- 一、js数据操作
- 1. 常用操作
- 1.1 其他类型转字符串
- 1.2 其他类型转Boolean
- 1.3 判断赋值无意义的情况下使用默认值
- 1.4 判断条件成立的情况下执行右侧表达式
- 1.5 其他
- 二、引用类型的数据使用
- 1. Object类型数据
- 1.1 访问Object属性
- 1.2 添加、修改、删除属性
- 1.3 遍历属性
- 2. Array类型数据
- 三、数据拷贝
- 1. 浅拷贝
- 1.1 扩展运算符方式
- 1.2 Object.assign()
- 2. 深拷贝
- 2.1 使用 JSON.parse() 和 JSON.stringify()配合完成
- 2.2 使用递归函数循环复制对象或数组
- 2.3 使用第三方库
- 四、js原型链
- 五、总结
引言
本系列教程旨在帮助一些零基础的玩家快速上手前端开发。基于我自学的经验会删减部分使用频率不高的内容,并不代表这部分内容不重要,只是对于初学者来说没必要一开始就学的面面俱到。我希望可以先通过主干内容带大家入门前端,细节技巧性内容,可以在后续的开发工作中自行发现并掌握。
在上一篇【前端开发入门】JavaScript快速入门–js变量 中,我们已经熟悉了js定义变量的技巧,本篇主要介绍js变量的使用技巧(数据操作)。
一、js数据操作
首先要认识js的两个概念
赋值
和表达式
- 赋值:用
=
等号连接左右两边,将右侧内容赋值给左侧变量。 - 表达式:使用js运算符进行两个值(也可以是变量)之间的计算,并将整体视为一个表达式。
我们来曲解一下 表达式
的定义,以便更好的理解这个东西。
我们可以把表达式认定为一个 东西
:
- 一个常量值:1 、“hello world !” 、true 、[1, 2, 3, 4, 5] 、 { name: ‘张三’, age: 28, sex: ‘男’ } 等等。
- 一个算式:1+1 、Math.max(1, 2)
- 一个函数调用结果的返回值:[1,2,3,4,5].find( item => item == 2)、add(1,2)
- 一个函数:function F1(){} 、 ()=>{}
- 以上的内容都有一个共同的特点,他们不论进行了多么复杂的运行逻辑,最终都表示一个确定的东西。最终这一整段的表达式可以抽象为一个整体,参与到赋值操作、其他表达式运算或是函数传参等逻辑中去。
1. 常用操作
js是一个动态语言,一些类型可以随意变换,但是要时刻警惕转换后的类型是否是你需要的结果。
以下为常用操作,但不包含所有的符号操作。js的运算符以及表达式全部内容请参考:表达式与运算符
1.1 其他类型转字符串
当需要快速获得字符串时,测试如下代码
// 字符串加任意Number、Boolean、undefined、null都会做字面拼接转换为字符串
console.log("str" + 1111);// 输出: str1111
console.log("str" + true);// 输出: strtrue
console.log("str" + false);// 输出: strfalse
console.log("str" + undefined);// 输出: strundefined
console.log("str" + null);// 输出: strnull
// 遵循加法执行顺序
console.log(1 + 2 + "str");// 输出: 3str
// 当需要快速转为字符串时可以直接使用空字符串 '' 拼接以上类型数据
1.2 其他类型转Boolean
当需要进行逻辑判断时,测试如下代码
// 在js中一些类型的值可以视为true
const condition1 = true;
const condition2 = 12; // 数字不为0即为true
const condition3 = "hello"; // 字符串不为空即为true
const condition4 = []; // 只要有个括号在就算true
const condition5 = {}; // 同上
// 以下被视为false
const condition6 = false;
const condition7 = 0;
const condition8 = "";
const condition9 = undefined;
const condition10 = null;
const condition11 = NaN;
if (condition4) {
console.log("hello world");
}
1.3 判断赋值无意义的情况下使用默认值
js中
||
符号代表如果前边那个表达式能用(有内容的,参考1.2中的表达式视为true的情况),就直接用。如果前边那个表达式不能用(false),那就用后边那个(无论后边那个是true还是false)。
换句话说||
符号就是为了尽可能寻找一个可用的表达式(true),从左侧到右侧依次寻找,找到即输出该表达式,如果没找到那就只好输出最后那个不可用表达式(false)
测试如下代码(注意区分定义函数形参与调用函数的实参区别):
const data1 = undefined
const data2 = "true data"
const actionFunc = (data) => {
// 当传参无意义,则提供一个默认值给useData
const useData = data || 'default data'
console.log(useData);
}
actionFunc(data1)
actionFunc(data2)
1.4 判断条件成立的情况下执行右侧表达式
js中
&&
符号代表如果前边那个表达式不可用(false),那就直接输出这个表达式。如果可用(有内容的,参考1.2中的表达式视为true的情况),那就输出后边那个表达式(不论后边那个是否为true)
换句话说&&
符号就是为了尽可能找一个不可用的表达式(false),从左侧到右侧依次寻找,找到即输出该表达式,如果没找到那就只好输出最后那个可用表达式(true)
const data1 = undefined
const data2 = {
next: () => {
console.log('next action');
return '执行完成'
}
}
const actionFunc = (data) => {
// 判断函数执行的主体是否存在,当存在时再执行具体函数,确保函数正确执行。
const useData = data && data.next()
console.log('useData', useData);
}
actionFunc(data1)
actionFunc(data2)
1.5 其他
其他逻辑操作参考:表达式与运算符
高阶数学计算参考:Math
二、引用类型的数据使用
引用类型的数据主要是
Object
和Array
两种,function单独说
提前说一个小技巧 扩展运算符
:
利用 ...
(三个英文键盘的 .
符号)将Object、Array的外壳({}、[])打开,将里边的元素平铺到另一个Object、Array中(注意只能平铺第一层,如果Object、Array中嵌套了其他Object、Array,那么会原样输出被嵌套部分),例如
// 平铺Object
const Obj1 = {
name: "qbbmnn",
age: 18,
sex: "男",
};
const Obj2 = {
...Obj1,
hobby: "女",
};
console.log(Obj2); // 输出: {name: 'qbbmnn', age: 18, sex: '男', hobby: '女'}
// 平铺Array
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4]
console.log(arr2); // 输出: [1, 2, 3, 4]
// 嵌套情况以Array为例,嵌套部分不会展开,作为一个整体子元素跟随同级展开
const arr1 = [1, 2, 3, [1, 2]];
const arr2 = [...arr1, 4];
console.log(arr2); // 输出: [1, 2, 3, Array(2), 4]
1. Object类型数据
Object 类型数据是以大括号包裹键值对的形式存在,,键名为字符串形式,键值可以为任意类型。
1.1 访问Object属性
const Obj1 = {
name: "qbbmnn",
age: 18,
sex: "男",
project: {
name: "test",
},
};
// 两种访问属性的方式都可以
console.log(Obj1.name);// 输出: qbbmnn
console.log(Obj1["name"]);// 输出: qbbmnn
// 遇到嵌套情况同理
console.log(Obj1.project.name);// 输出: test
console.log(Obj1["project"]["name"]);// 输出: test
// 遇到不确定属性存在与否的情况,获取的数据并不一定满足当前需要,可能缺斤短两
// 操作符为 `?.` ,表示判断接下来的属性是否存在,存在就正常输出,不存在就返回undefined
console.log(Obj1?.name);// 输出: qbbmnn
console.log(Obj1?.time);// 输出: undefined
console.log(Obj1?.project?.name);// 输出: test
console.log(Obj1?.project?.time);// 输出: undefined
1.2 添加、修改、删除属性
js是一门动态语言,可以随时向一个Object中添加或移除属性。遇到获取数据不符合前端渲染条件时,即使用Object的操作方法将数据进行改造。
- 添加、修改属性:直接给属性赋值即可。
- 删除属性::使用
delete
关键字。
// 添加属性
Obj1.time = new Date();
// 或
Obj1["time"] = new Date();
// 修改属性
Obj1.age = 19;
// 或
Obj1["age"] = 19;
// 删除属性,`delete关键字+ 对象属性名`
delete Obj1.project;
// 最终打印
console.log(Obj1);// 输出: {name: 'qbbmnn', age: 19, sex: '男', time: Thu Oct 31 2024 11:48:03 GMT+0800 (中国标准时间)}
1.3 遍历属性
尝试输入以下代码,运行调试查看结果
const Obj1 = {
name: "qbbmnn",
age: 18,
sex: "男",
};
// 方法1
for (key in Obj1) {
console.log("key =", key);
console.log("value =", Obj1[key]);
}
// 方法2,Object为关键字,Object对象。
const keys = Object.keys(Obj1);
console.log(keys);
const values = Object.values(Obj1);
console.log(values);
const entries = Object.entries(Obj1);
console.log(entries);
console.log(entries[0]);
console.log(entries[1]);
console.log(entries[2]);
2. Array类型数据
数组的基础操作请参考:JavaScript 数组方法
在上述网站中详细列举了js数组的操作方法,重点在于掌握写法,在实际开发过程中所操作的数组往往更为复杂。
数组操作一般为按特征查询、按特征分类、以及批量新增属性。
准备一组数据:
const users = [
{ name: '张三', age: 28, sex: '男' },
{ name: '李四', age: 22, sex: '女' },
{ name: '王五', age: 35, sex: '男' },
{ name: '赵六', age: 29, sex: '女' }
];
设定一个场景1:找出年龄在28岁的一组信息
const target = users.find(item => {
return item.age == 28
})
// 此时将返回指定的那组数据对象,而不是一个数组
console.log(target);
设定一个场景2:找出年龄在28岁及以下的一组信息
const targetArr = users.filter(item => {
return item.age <= 28
})
// 此时将返回一个数组
console.log(targetArr);
设定一个场景3:为所有数据添加班级属性
const targetArr = users.map(item => {
return {
...item,
class: "1班"
}
})
// 此时将返回指定的那组数据对象,而不是一个数组
console.log(targetArr);
三、数据拷贝
数据拷贝这一问题主要针对引用类型数据。引用类型数据的赋值给不同的变量,是将对象的内存地址赋值给不同的变量,当其中一个发生改变,则所有的变量都会跟着变。这往往会导致一些意想不到的bug,所以对引用类型数据进行拷贝是非常重要的。
对引用类型数据拷贝分为 :浅拷贝
、深拷贝
两种。
- 浅拷贝 只复制对象的第一层,对于嵌套的对象或数组,仍然是引用复制。
- 深拷贝 会递归地复制对象的所有层级,确保原始对象和拷贝对象之间没有引用关系。
1. 浅拷贝
浅拷贝只会复制对象的一层,对于对象内部的引用类型(比如嵌套的对象或数组),它只是复制了引用而不是创建新的副本。这意味着如果原始对象中的某个属性是一个对象或数组,那么浅拷贝后的对象中的该属性仍然指向同一个内存地址。
1.1 扩展运算符方式
const original = { a: 1, b: { c: 2 } };
const shallowCopy = { ...original };
// 修改原始对象中的第一层基本数据类型
original.a = 3;
// 修改原始对象中的嵌套对象
original.b.c = 3;
console.log(original.a); // 输出:3
console.log(shallowCopy.a); // 输出:1
console.log(original.b); // 输出:{ c: 3 }
console.log(shallowCopy.b); // 输出:{ c: 3 }
1.2 Object.assign()
Object的静态方法,将一个或者多个源对象中所有可枚举的自有属性复制到目标对象,并返回修改后的目标对象。
// 其他代码同扩展运算符方式浅拷贝
const shallowCopy = Object.assign({}, original);
2. 深拷贝
深拷贝会递归地复制对象的所有层级,包括嵌套的对象或数组。这意味着原始对象和拷贝对象之间没有任何引用关系,对其中一个对象的修改不会影响另一个对象。
2.1 使用 JSON.parse() 和 JSON.stringify()配合完成
这种方法只能处理属性值为一般常见的对象和数组,不能处理属性值为:函数、日期对象、正则表达式对象等非标准类型。
这个方法的原理是将对象或数组转为字符串,进行基本数据类型的值拷贝,拷贝完成后再解码成为对象或数组。
const original = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(original));
// 修改原始对象中的嵌套对象
original.b.c = 3;
console.log(original); // 输出:{ a: 1, b: { c: 3 } }
console.log(deepCopy); // 输出:{ a: 1, b: { c: 2 } }
2.2 使用递归函数循环复制对象或数组
function isObject(obj) {
return obj != null && typeof obj === 'object';
}
function deepClone(obj, hash = new WeakMap()) {
if (!isObject(obj)) return obj;
if (hash.has(obj)) return hash.get(obj);
let cloneObj = Array.isArray(obj) ? [] : {};
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
const original = { a: 1, b: { c: 2 } };
const deepCopy = deepClone(original);
// 修改原始对象中的嵌套对象
original.b.c = 3;
console.log(original); // 输出:{ a: 1, b: { c: 3 } }
console.log(deepCopy); // 输出:{ a: 1, b: { c: 2 } }
2.3 使用第三方库
这里只做推荐,因为还没讲到第三方库引用的部分。
- Lodash ,文档参考:lodash深拷贝
- radash ,文档参考:radash 深拷贝
以上两个库都包含了大量js操作方法,相较于自己写的方法,这些库都经历了各种环境的验证,使用起来会更加稳定。建议自己写只做思路学习,实际上生产环境尽量使用这种比较稳定的第三方库,保证代码执行结果的一致性。
四、js原型链
这其实是一个非常简单的概念,先试图理解一个前提条件:你所定义的js变量如字符串、数组、布尔值、对象、数组等这些都不是凭空而来的。它们都对应了一个创建它们的原型类,分别是String、Number、Boolean、Object、Array等。js在设计这些类时会默认提供一些方法供我们使用。
举个例子:你是你家庭实例化的一个对象,在外边看你身上什么都没有,但是你默认可以使用家里所有的物品。
在以下图片中可以看到,数组定义时并没有提供 map
方法,这些方法都是来自于 Array
定义的。
通常情况下这些方法或属性都是可以直接使用的,当你不清楚一个变量有什么可用方法时,可以通过 __proto__
属性来查询一下。这个属性表示当前变量家里有什么固有的方法及属性。
如图上代码测试后可以发现,Array其实是Object构建出来的。把 users.__proto__
作为一个整体(Array),可以继续使用 __proto__
属性查询他的父级的默认属性和方法,直到查询到 null
就到头了。
例子中users也可以使用Object的固有方法例如:users.toLocaleString()
,这里就好比你除了可以使用你父母家的物品,当然可以使用你爷爷家里的物品。
如此一级一级往上查询属性及方法,就构成了js的原型链,这条链路上的东西都可以直接拿来用, __proto__
只是用来辅助查询有哪些内容,实际使用时可以直接像调用Object属性或方法一样使用 .
符号。
五、总结
以上即为js数据操作的大体内容,试图理解原型链,掌握引用类型的数据使用方法,进而支持自己可以编写一系列逻辑运算代码,同时利用深浅拷贝规避一些意想不到的bug。当你可以熟练的编写js代码逻辑后,那么就可以进入下一阶段函数封装。
再接再厉~