初中级前端 JavaScript 自测清单

「JavaScript 对象」为主,大致包括以下内容:
在这里插入图片描述

一、对象

JavaScript 有八种数据额类型,有七种原始类型,它们值只包含一种类型(字符串,数字或其他),而对象是用来「保存键值对和更复杂实体。」我们可以通过使用带有可选「属性列表」的花括号 {…} 来创建对象,一个属性就是一个键值对 {“key” : “value”} ,其中键( key )是一个字符串(或称属性名),值( value )可以是任何类型。

1. 创建对象
我们可以使用 2 种方式来创建一个新对象:

// 1. 通过“构造函数”创建
let user = new Object();

// 2. 通过“字面量”创建
let user = {};

2. 对象文本和属性
创建对象时,可以初始化对象的一些属性:


let user = {
 name : 'leo',
  age  : 18
}

然后可以对该对象进行属性对「增删改查」操作:

// 增加属性
user.addr = "China";
// user => {name: "leo", age: 18, addr: "China"}

// 删除属性
delete user.addr
// user => {name: "leo", age: 18}

// 修改属性
user.age  = 20;
// user => {name: "leo", age: 20}

// 查找属性
user.age;
// 20

3. 方括号的使用
当然对象的键( key )也可以是多词属性,但必须加引号,使用的时候,必须使用方括号( [] )读取:

let user = {
 name : 'leo',
  "my interest" : ["coding", "football", "cycling"]
}
user["my interest"]; // ["coding", "football", "cycling"]
delete user["my interest"];

我们也可以在方括号中使用变量,来获取属性值:

let key = "name";
let user = {
 name : "leo",
  age  : 18 
}
// ok
user[key]; // "leo"
user[key] = "pingan";

// error
user.key; // undefined

4. 计算属性
创建对象时,可以在对象字面量中使用方括号,即 「计算属性」 :

let key = "name";
let inputKey = prompt("请输入key", "age");
let user = {
 [key] : "leo",
  [inputKey] : 18
}
// 当用户在 prompt 上输入 "age" 时,user 变成下面样子:
// {name: "leo", age: 18}

当然,计算属性也可以是表达式:

let key = "name";
let user = {
 ["my_" + key] : "leo"
}
user["my_" + key]; // "leo"

5. 属性名简写
实际开发中,可以将相同的属性名和属性值简写成更短的语法:

// 原本书写方式
let getUser = function(name, age){
  // ...
 return {
  name: name,
    age: age
 }
}

// 简写方式
let getUser = function(name, age){
  // ...
 return {
  name,
    age
 }
}

也可以混用:

// 原本书写方式
let getUser = function(name, age){
  // ...
 return {
  name: name,
    age: 18
 }
}

// 简写方式
let getUser = function(name, age){
  // ...
 return {
  name,
    age: 18
 }
}

6. 对象属性存在性检测

6.1 使用 in 关键字
该方法可以判断「对象的自有属性和继承来的属性」是否存在。

let user = {name: "leo"};
"name" in user;            //true,自有属性存在
"age"  in user;            //false
"toString" in user;     //true,是一个继承属性

这个在判断一个对象是否有某个属性的时候,比较有用
6.2使用对象的 hasOwnProperty() 方法。
该方法只能判断「自有属性」是否存在,对于「继承属性」会返回 false 。

let user = {name: "leo"};
user.hasOwnProperty("name");       //true,自有属性中有 name
user.hasOwnProperty("age");        //false,自有属性中不存在 age
user.hasOwnProperty("toString");   //false,这是一个继承属性,但不是自有属性

6.3 用 undefined 判断
该方法可以判断对象的「自有属性和继承属性」。

let user = {name: "leo"};
user.name !== undefined;        // true
user.age  !== undefined;        // false
user.toString !== undefined     // true

该方法存在一个问题,如果属性的值就是 undefined 的话,该方法不能返回想要的结果:

let user = {name: undefined};
user.name !== undefined;        // false,属性存在,但值是undefined
user.age  !== undefined;        // false
user.toString !== undefined;    // true

6.4 在条件语句中直接判断

let user = {};
if(user.name) user.name = "pingan";
//如果 name 是 undefine, null, false, " ", 0 或 NaN,它将保持不变

user; // {}

7. 对象循环遍历
当我们需要遍历对象中每一个属性,可以使用 for…in 语句来实现
7.1 for…in 循环
for…in 语句以任意顺序遍历一个对象的除 Symbol 以外的可枚举属性。「注意」 :for…in 不应该应用在一个数组,其中索引顺序很重要。


let user = {
 name : "leo",
  age  : 18
}

for(let k in user){
 console.log(k, user[k]);
}
// name leo
// age 18

7.2 ES7 新增方法
ES7中新增加的 Object.values()和Object.entries()与之前的Object.keys()类似,返回数组类型。
1. Object.keys()
返回一个数组,成员是参数对象自身的(不含继承的)所有「可遍历属性」的健名。

let user = { name: "leo", age: 18};
Object.keys(user); // ["name", "age"]

2. Object.values()
返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值。

let user = { name: "leo", age: 18};
Object.values(user); // ["leo", 18]

如果参数不是对象,则返回空数组:

Object.values(10);   // []
Object.values(true); // []

3. Object.entries()
返回一个数组,成员是参数对象自身的(不含继承的)所有「可遍历属性」的键值对数组。

let user = { name: "leo", age: 18};
Object.entries(user);
// [["name","leo"],["age",18]]

手动实现Object.entries()方法:

// Generator函数实现:  
function* entries(obj){
    for (let k of Object.keys(obj)){
        yield [k ,obj[k]];
    }
}
// 非Generator函数实现:
function entries (obj){
    let arr = [];
    for(let k of Object.keys(obj)){
        arr.push([k, obj[k]]);
    }
    return arr;
}

4. Object.getOwnPropertyNames(Obj)
该方法返回一个数组,它包含了对象 Obj 所有拥有的属性(「无论是否可枚举」)的名称

let user = { name: "leo", age: 18};
Object.getOwnPropertyNames(user);
// ["name", "age"]

二、对象拷贝
1. 赋值操作
首先回顾下基本数据类型和引用数据类型:

基本类型
概念:基本类型值在内存中占据固定大小,保存在栈内存中(不包含闭包中的变量)。常见包括:undefined,null,Boolean,String,Number,Symbol

引用类型
概念:引用类型的值是对象,保存在堆内存中。而栈内存存储的是对象的变量标识符以及对象在堆内存中的存储地址(引用),引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。常见包括:Object,Array,Date,Function,RegExp等
1.1 基本数据类型赋值
在栈内存中的数据发生数据变化的时候,系统会自动为新的变量分配一个新的之值在栈内存中,两个变量相互独立,互不影响的。

let user  = "leo";
let user1 = user;
user1 = "pingan";
console.log(user);  // "leo"
console.log(user1); // "pingan" 

1.2 引用数据类型赋值
在 JavaScript 中,变量不存储对象本身,而是存储其“内存中的地址”,换句话说就是存储对其的“引用”。如下面 leo 变量只是保存对user 对象对应引用:

let user = { name: "leo", age: 18};
let leo  = user;

其他变量也可以引用 user 对象:

let leo1 = user;
let leo2 = user

但是由于变量保存的是引用,所以当我们修改变量 leo \ leo1 \ leo2 这些值时,「也会改动到引用对象」 user ,但当 user 修改,则其他引用该对象的变量,值都会发生变化:

leo.name = "pingan";
console.log(leo);   // {name: "pingan", age: 18}
console.log(leo1);  // {name: "pingan", age: 18}
console.log(leo2);  // {name: "pingan", age: 18}
console.log(user);  // {name: "pingan", age: 18}

user.name = "pingan8787";
console.log(leo);   // {name: "pingan8787", age: 18}
console.log(leo1);  // {name: "pingan8787", age: 18}
console.log(leo2);  // {name: "pingan8787", age: 18}
console.log(user);  // {name: "pingan8787", age: 18}

这个过程中涉及变量地址指针指向问题,这里暂时不展开讨论,有兴趣的朋友可以网上查阅相关资料。
2. 对象比较

let user = { name: "leo", age: 18};
let leo  = user;
let leo1 = user;
leo ==  leo1;   // true
leo === leo1;   // true
leo ==  user;   // true
leo === user;   // true

但如果两个变量是空对象 {} ,则不相等:

let leo1 = {};
let leo2 = {};
leo1 ==  leo2;  // false
leo1 === leo2;  // false

3. 浅拷贝
3.1 概念
概念:「新的对象复制已有对象中非对象属性的值和对象属性的引用」。也可以理解为:「一个新的对象直接拷贝已存在的对象的对象属性的引用」,即浅拷贝。

浅拷贝「只对第一层属性进行了拷贝」,当第一层的属性值是基本数据类型时,新的对象和原对象互不影响,但是如果第一层的属性值是复杂数据类型,那么新对象和原对象的属性值其指向的是同一块内存地址。

通过示例代码演示没有使用浅拷贝场景:

// 示例1 对象原始拷贝
let user = { name: "leo", skill: { JavaScript: 90, CSS: 80}};
let leo = user;
leo.name = "leo1";
leo.skill.CSS = 90;
console.log(leo.name);      // "leo1"
console.log(user.name);     // "leo1"
console.log(leo.skill.CSS); // 90
console.log(user.skill.CSS);// 90

// 示例2 数组原始拷贝
let user = ["leo", "pingan", {name: "pingan8787"}];
let leo  = user;
leo[0] = "pingan888";
leo[2]["name"] = "pingan999";
console.log(leo[0]);          // "pingan888"
console.log(user[0]);         // "pingan888"
console.log(leo[2]["name"]);  // "pingan999"
console.log(user[2]["name"]); // "pingan999"

从上面示例代码可以看出:由于对象被直接拷贝,相当于拷贝 「引用数据类型」 ,所以在新对象修改任何值时,都会改动到源数据。

接下来实现浅拷贝,对比以下
3.2 实现浅拷贝

  1. Object.assign()
    语法:Object.assign(target, …sources)ES6中拷贝对象的方法,接受的第一个参数是拷贝的目标target,剩下的参数是拷贝的源对象sources(可以是多个)。详细介绍,可以阅读文档《MDN Object.assign》。
// 示例1 对象浅拷贝
let user = { name: "leo", skill: { JavaScript: 90, CSS: 80}};
let leo = Object.assign({}, user);
leo.name = "leo1";
leo.skill.CSS = 90;
console.log(leo.name);      // "leo1" ⚠️ 差异!
console.log(user.name);     // "leo"  ⚠️ 差异!
console.log(leo.skill.CSS); // 90
console.log(user.skill.CSS);// 90

// 示例2 数组深拷贝
let user = ["leo", "pingan", {name: "pingan8787"}];
let leo  = user;
leo[0] = "pingan888";
leo[2]["name"] = "pingan999";
console.log(leo[0]);          // "pingan888"  ⚠️ 差异!
console.log(user[0]);         // "leo"        ⚠️ 差异!
console.log(leo[2]["name"]);  // "pingan999"
console.log(user[2]["name"]); // "pingan999"

从打印结果可以看出,浅拷贝只是在根属性(对象的第一层级)创建了一个新的对象,但是对于属性的值是对象的话只会拷贝一份相同的内存地址。
Object.assign() 使用注意:

只拷贝源对象的自身属性(不拷贝继承属性);
不会拷贝对象不可枚举的属性;
属性名为Symbol 值的属性,可以被Object.assign拷贝;
undefined和null无法转成对象,它们不能作为Object.assign参数,但是可以作为源对象。

Object.assign(undefined); // 报错
Object.assign(null);      // 报错

Object.assign({}, undefined); // {}
Object.assign({}, null);      // {}

let user = {name: "leo"};
Object.assign(user, undefined) === user; // true
Object.assign(user, null)      === user; // true

2. Array.prototype.slice()
语法:arr.slice([begin[, end]])slice() 方法返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)。原始数组不会被改变

// 示例 数组深拷贝
let user = ["leo", "pingan", {name: "pingan8787"}];
let leo  = Array.prototype.slice.call(user);
leo[0] = "pingan888";
leo[2]["name"] = "pingan999";
console.log(leo[0]);          // "pingan888"  ⚠️ 差异!
console.log(user[0]);         // "leo"        ⚠️ 差异!
console.log(leo[2]["name"]);  // "pingan999"
console.log(user[2]["name"]); // "pingan999"

3. Array.prototype.concat()
语法:var new_array = old_array.concat(value1[, value2[, …[, valueN]]])concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组

let user  = [{name: "leo"},   {age: 18}];
let user1 = [{age: 20},{addr: "fujian"}];
let user2 = user.concat(user1);
user1[0]["age"] = 25;
console.log(user);  // [{"name":"leo"},{"age":18}]
console.log(user1); // [{"age":25},{"addr":"fujian"}]
console.log(user2); // [{"name":"leo"},{"age":18},{"age":25},{"addr":"fujian"}]

Array.prototype.concat 也是一个浅拷贝,只是在根属性(对象的第一层级)创建了一个新的对象,但是对于属性的值是对象的话只会拷贝一份相同的内存地址。
4. 拓展运算符(…)
语法:var cloneObj = { …obj };扩展运算符也是浅拷贝,对于值是对象的属性无法完全拷贝成2个不同对象,但是如果属性都是基本类型的值的话,使用扩展运算符也是优势方便的地方。

let user = { name: "leo", skill: { JavaScript: 90, CSS: 80}};
let leo = {...user};
leo.name = "leo1";
leo.skill.CSS = 90;
console.log(leo.name);      // "leo1" ⚠️ 差异!
console.log(user.name);     // "leo"  ⚠️ 差异!
console.log(leo.skill.CSS); // 90
console.log(user.skill.CSS);// 90

3.3 手写浅拷贝
实现原理:新的对象复制已有对象中非对象属性的值和对象属性的「引用」,也就是说对象属性并不复制到内存。

function cloneShallow(source) {
    let target = {};
    for (let key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
            target[key] = source[key];
        }
    }
    return target;
}
「for in」
for...in语句以任意顺序遍历一个对象自有的、继承的、可枚举的、非Symbol的属性。对于每个不同的属性,语句都会被执行。

「hasOwnProperty」
该函数返回值为布尔值,所有继承了 Object 的对象都会继承到 hasOwnProperty 方法,和 in 运算符不同,该函数会忽略掉那些从原型链上继承到的属性和自身属性。语法:obj.hasOwnProperty(prop)prop 是要检测的属性「字符串名称」或者Symbol。

4. 深拷贝

4.1 概念
复制变量值,对于引用数据,则递归至基本类型后,再复制。深拷贝后的对象「与原来的对象完全隔离」,互不影响,对一个对象的修改并不会影响另一个对象。
**```
**4.2 实现深拷贝**
1. JSON.parse(JSON.stringify())(最常用,但是非资源)**
其原理是把一个对象序列化成为一个JSON字符串,将对象的内容转换成字符串的形式再保存在磁盘上,再用JSON.parse() 反序列化将JSON字符串变成一个新的对象

```bash
let user = { name: "leo", skill: { JavaScript: 90, CSS: 80}};
let leo = JSON.parse(JSON.stringify(user));
leo.name = "leo1";
leo.skill.CSS = 90;
console.log(leo.name);      // "leo1" ⚠️ 差异!
console.log(user.name);     // "leo"  ⚠️ 差异!
console.log(leo.skill.CSS); // 90 ⚠️ 差异!
console.log(user.skill.CSS);// 80 ⚠️ 差异!

JSON.stringify() 使用注意:

拷贝的对象的值中如果有函数, undefined , symbol 则经过 JSON.stringify() `序列化后的JSON字符串中这个键值对会消失;
无法拷贝不可枚举的属性,无法拷贝对象的原型链;
拷贝 Date 引用类型会变成字符串;
拷贝 RegExp 引用类型会变成空对象;
对象中含有 NaN 、 Infinity 和 -Infinity ,则序列化的结果会变成 null ;
无法拷贝对象的循环应用(即 obj[key] = obj )。
2. 第三方库
4.3 手写深拷贝
核心思想是「递归」,遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。实现代码:

const isObject = obj => typeof obj === 'object' && obj != null;

function cloneDeep(source) {
    if (!isObject(source)) return source; // 非对象返回自身
    const target = Array.isArray(source) ? [] : {};
    for(var key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
            if (isObject(source[key])) {
                target[key] = cloneDeep(source[key]); // 注意这里
            } else {
                target[key] = source[key];
            }
        }
    }
    return target;
}

该方法缺陷:遇到循环引用,会陷入一个循环的递归过程,从而导致爆栈。其他写法,可以阅读《如何写出一个惊艳面试官的深拷贝?》 。

5. 小结

「浅拷贝」:将对象的每个属性进行依次复制,但是当对象的属性值是引用类型时,实质复制的是其引用,当引用指向的值改变时也会跟着变化。

「深拷贝」:复制变量值,对于引用数据,则递归至基本类型后,再复制。深拷贝后的对象「与原来的对象完全隔离」,互不影响,对一个对象的修改并不会影响另一个对象。

「深拷贝和浅拷贝是针对复杂数据类型来说的,浅拷贝只拷贝一层,而深拷贝是层层拷贝。」
![在这里插入图片描述](https://img-blog.csdnimg.cn/9572ea6c93f14c1ab5aafc210a01f9f5.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5a2m5Lmg5LiA55u05Zyo6Lev5LiK,size_20,color_FFFFFF,t_70,g_se,x_16)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

学习一直在路上

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值