theme: channing-cyan
一、数据类型
1. JavaScript共有八种数据类型
其中 Symbol和 BigInt是 ES6中新增的数据类型:
- Symbol代表创建后独一无二且不可变的数据类型,它主要是为了解决可能出现的全局变量冲突的问题。
- BigInt 是一种数字类型的数据,它可以表示任意精度格式的整数,使用 BigInt可以安全地存储和操作大整数,即使这个数已经超出了 Number能够表示的安全整数范围。
这些数据可以分为原始数据类型和引用数据类型:
- 栈:原始数据类型(Undefined、Null、Boolean、Number、String)
- 堆:引用数据类型(对象、数组和函数)
2.区别
两种类型的区别在于存储位置的不同:
- 原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
- 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
堆和栈的概念存在于数据结构和操作系统内存中,在数据结构中:
- 在数据结构中,栈中数据的存取方式为先进后出。
- 堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。
在操作系统中,内存被分为栈区和堆区:
- 栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
- 堆区内存一般由开发着分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。
3.数据类型检测的方式
typeof
console.log(typeof 2); // number
console.log(typeof true); // boolean
console.log(typeof 'str'); // string
console.log(typeof function(){}); // function
console.log(typeof undefined); // undefined
console.log(typeof {}); // object
console.log(typeof []); // object
console.log(typeof null); // object
//其中数组、对象、null都会被判断为object,其他判断都正确。
instanceof
instanceof 可以正确判断对象的类型,其内部运行机制是判断在其 原型链中 能否找到 该类型的原型。
console.log(2 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log('str' instanceof String); // false
console.log([] instanceof Array); // true
console.log(function(){} instanceof Function); // true
console.log({} instanceof Object); // true
可以看到:
- instanceof 只能正确判断 引用 数据类型,而不能判断 基本 数据类型。
- instanceof 运算符可以用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。
constructor
console.log((2).constructor === Number); // true
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true
constructor有两个作用:
- 判断数据的类型
- 对象实例通过 constrcutor 对象访问它的构造函数。
需要注意,如果创建一个对象来改变它的原型,constructor 就不能用来判断数据类型了:
function Fn(){};
Fn.prototype = new Array();
var f = new Fn();
console.log(f.constructor===Fn); // false
console.log(f.constructor===Array); // true
Object.prototype.toString.call()
Object.prototype.toString.call() 使用 Object 对象的原型方法 toString 来判断数据类型:
const a = Object.prototype.toString;
console.log(a.call(2)); // [object Number]
console.log(a.call(true)); // [object Boolean]
console.log(a.call('str')); // [object String]
console.log(a.call([])); // [object Array]
console.log(a.call(function(){})); // [object Function]
console.log(a.call({})); // [object Object]
console.log(a.call(undefined)); // [object Undefined]
console.log(a.call(null)); // [object Null]
obj.toString() 的结果和 Object.prototype.toString.call(obj) 的结果不一样,这是为什么?
这是因为 toString是 Object 的原型方法,而 Array、function 等类型作为 Object 的实例,都重写了 toString 方法。不同的对象类型调用 toString 方法时,根据原型链的知识,调用的是对应的重写之后的 toString() 方法( function类型返回内容为函数体的字符串,Array类型返回元素组成的字符串…),而不会去调用 Object上原型 toString() 方法 (返回对象的具体类型),所以采用 obj.toString() 不能得到其对象类型,只能将 **obj **转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用 Object 原型上的 toString() 方法。
4.null和undefined区别
- 首先 undefined 和 Null 都是 基本 数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。
- undefined 代表的含义是 未定义,null 代表的含义是 空对象。
- 一般变量声明了但还没有定义的时候会返回 undefined。
- null主要用于赋值给一些可能会返回对象的变量。
- 作为初始化。undefined在 JavaScript中不是一个保留字,这意味着可以使用 **undefined **来作为一个变量名,但是这样的做法是非常危险的,它会影响对 undefined值的判断。我们可以通过一些方法获得安全的 undefined 值,比如说 void 0。
- 当对这两种类型使用 typeof进行判断时,Null类型化会返回 ’ object’,这是一个历史遗留的问题。当使用双等号对两种类型的值进行比较时会返回 true,使用三个等号时会返回 false。
5.intanceof 操作符的实现原理及实现
instanceof运算符用于判断构造函数的 prototype属性是否出现在对象的原型链中的任何位置。
function myInstanceof(left, right) {
// 获取对象的原型
let proto = Object.getPrototypeOf(left)
// 获取构造函数的 prototype 对象
let prototype = right.prototype;
// 判断构造函数的 prototype 对象是否在对象的原型链上
while (true) {
if (!proto) return false;
if (proto === prototype) return true; // 如果没有找到,就继续从其原型上找,Object.getPrototypeOf方法用来获取指定对象的原型
proto = Object.getPrototypeOf(proto);
} }
6.typeof NaN 的结果是什么?
NaN指“不是一个数字”(not a number)用于指出数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果”
console.log(typeof NaN) // number
console.log(NaN !== NaN) // true
:::info
NaN 是一个特殊值,它和自身不相等,是唯一一个非自反(自反,reflexive,即 **x === x **不成立)的值。而 NaN !== NaN 为 true。
:::
7.isNaN 和 Number.isNaN 函数的区别?
isNaN
isNaN接收参数后,会尝试将这个参数转换为数值,如果转换后为 number类型,则返回false,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true,会影响 **NaN *的判断。
console.log(isNaN(null)); //false
console.log(isNaN(true)); //false
console.log(isNaN(false)); //false
console.log(isNaN(0)); //false
console.log(isNaN(undefined)); //true
console.log(isNaN("AB")); //true
console.log(isNaN({a: 1})); //true
console.log(isNaN(NaN)); //true
Number.isNaN
Es6新增的,不会进行数据类型的转换,只有 NaN才返回true,其余都是 false
console.log(Number.isNaN(null)); //false
console.log(Number.isNaN(true)); //false
console.log(Number.isNaN(false)); //false
console.log(Number.isNaN(0)); //false
console.log(Number.isNaN(undefined)); //false
console.log(Number.isNaN("AB")); //false
console.log(Number.isNaN({a: 1})); //false
console.log(Number.isNaN(NaN)); //true
二、数组
1.判断数组的方式
Object.prototype.toString.call()
Object.prototype.toString.call(arr).slice(8,-1) // Array
原型链
arr.__proto__ // Array.prototype
ES6的Array.isArray()
Array.isArray(arr) // true
instanceof
arr instanceof Array // true
Array.prototype.isPrototypeOf
Array.prototype.isPrototypeOf(arr) // true
2.数组有哪些原生方法?
toString()
把数组转成字符串
const fruits = ["Banana", "Orange", "Apple", "Mango"];
console.log(fruits.toString()); // Banana,Orange,Apple,Mango
join()
也可将所有数组元素结合为一个字符串。它的行为类似 toString(),但是您还可以规定分隔符:
const fruits = ["Banana", "Orange","Apple", "Mango"];
console.log(fruits.join(" * ")); // Banana * Orange * Apple * Mango
pop()
从数组中删除最后一个元素,返回被删除的值
const fruits = ["Banana", "Orange", "Apple", "Mango"];
console.log(fruits.pop()); // Mango
push()
(在数组结尾处)向数组添加一个新的元素,返回新数组的长度
const fruits = ["Banana", "Orange", "Apple", "Mango"];
const x = fruits.push("Kiwi"); // x 的值是 5
shift()
会删除首个数组元素,并把所有其他元素“位移”到更低的索引,返回被“位移出”的字符串
const fruits = ["Banana", "Orange", "Apple", "Mango"];
console.log(fruits.shift()); // Banana
unshift()
(在开头)向数组添加新元素,并“反向位移”旧元素,返回新数组的长度
const fruits = ["Banana", "Orange", "Apple", "Mango"];
console.log(fruits.unshift('Lemon')); // 4
splice()
向数组添加新项,返回一个包含已删除项的数组
const fruits = ["Banana", "Orange", "Apple", "Mango"];
// 使用 splice() 来拼接数组
fruits.splice(2, 0, "Lemon", "Kiwi");
// 第一个参数(2)定义了应添加新元素的位置(拼接)。
// 第二个参数(0)定义应删除多少元素。
// 其余参数(“Lemon”,“Kiwi”)定义要添加的新元素。
console.log(fruits) // ['Banana', 'Orange', 'Lemon', 'Kiwi', 'Apple', 'Mango']
// 使用 splice() 来删除元素
fruits.splice(0, 1); // 删除 fruits 中的第一个元素
// 第一个参数(0)定义新元素应该被添加(接入)的位置。
// 第二个参数(1)定义应该删除多个元素。
console.log(fruits) // ['Orange', 'Lemon', 'Kiwi', 'Apple', 'Mango']
concat()
通过合并(连接)现有数组来创建一个新数组,可以使用任意数量的数组参数。不影响原数组,它总是返回一个新数组
const myGirls = ["Cecilie", "Lone"];
const myBoys = ["Emil", "Tobias", "Linus"];
console.log(myGirls.concat(myBoys)) ; // 连接 myGirls 和 myBoys ['Cecilie', 'Lone', 'Emil', 'Tobias', 'Linus']
// 合并三个数组
const myBoysTwo = ["LIli", "JieJie"];
console.log(myGirls.concat(myBoys,myBoysTwo)); // 连接 myGirls 和 myBoys 和 myBoysTwo ['Cecilie', 'Lone', 'Emil', 'Tobias', 'Linus', 'LIli', 'JieJie']
// concat()可以将值作为参数
console.log(myBoysTwo.concat(['hahah'])) // ['LIli', 'JieJie', 'hahah']
slice()
用数组的某个片段切出新数组,不影响原数组
const myBoysTwo = ['LIli', 'JieJie', 'hahah']
myBoysTwo.slice(1) // ['JieJie', 'hahah']
myBoysTwo.slice(0,2) // ['LIli', 'JieJie']
reduce()
为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O7SoiM5o-1660817203338)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a248a43d09cf4be8bdf6efa17701866c~tplv-k3u1fbpfcp-zoom-1.image)]
callback (执行数组中每个值的函数,包含四个参数)
1、previousValue (上一次调用回调返回的值,或者是提供的初始值(initialValue))
2、currentValue (数组中当前被处理的元素)
3、index (当前元素在数组中的索引)
4、array (调用 reduce 的数组)
initialValue (作为第一次调用 callback 的第一个参数。)
解析 initialValue 参数
const arr = [1, 2, 3, 4];
const sum = arr.reduce(function(prev, cur, index, arr) {
console.log(prev, cur, index);
return prev + cur;
})
console.log(arr, sum);
// 1 2 1
// 3 3 2
// 6 4 3
// [1, 2, 3, 4] 10
这里可以看出index是从1开始的,第一次的prev的值是数组的第一个值。数组长度是4,但是reduce函数循环3次。
const arr = [1, 2, 3, 4];
const sum = arr.reduce(function(prev, cur, index, arr) {
console.log(prev, cur, index);
return prev + cur;
},0) //注意这里设置了初始值,一般来说我们提供初始值通常更安全
console.log(arr, sum);
// 0 1 0
// 1 2 1
// 3 3 2
// 6 4 3
// [1, 2, 3, 4] 10
这里可以看出index是从0开始的,第一次的prev的值是我们设置的初始值0,数组长度是4,reduce函数循环4次
:::info
结论:如果没有提供initialValue,reduce 会从索引1的地方开始执行 callback 方法,跳过第一个索引。如果提供initialValue,从索引0开始。
:::
数组去重
let arr = [1,2,3,4,4,1]
let newArr = arr.reduce((pre,cur)=>{
if(!pre.includes(cur)){
return pre.concat(cur)
}else{
return pre
}
},[])
console.log(newArr);// [1, 2, 3, 4]
let arr = [
{name: 'mom', id:1},
{name: 'timo', id:2},
{name: 'mom', id:1},
{name: 'timo', id:2},
]
let obj = {}
let newArr = arr.reduce((item, next)=>{
obj[next.id] ? "" : (obj[next.id] = true && item.push(next));
return item;
},[])
console.log(newArr);// [{name: 'mom', id:1}, {name: 'timo', id:2}]
3.数组的遍历方法
方法 | 是否改变原数组 | 特点 |
---|---|---|
forEach() | 否 | 数组方法,不改变原数组,没有返回值 |
map() | 否 | 数组方法,不改变原数组,有返回值,可链式调用 |
filter() | 否 | 数组方法,过滤数组,返回包含符合条件的元素的数组,可链式调用 |
for…of | 否 | for…of遍历具有Iterator迭代器的对象的属性,返回的是数组的元素、对象的属性值,不能遍历普通的obj对象,将异步循环变成同步循环 |
every() 和 some() | 否 | 数组方法,some()只要有一个是true,便返回true;而every()只要有一个是false,便返回false. |
find() 和 findIndex() | 否 | 数组方法,find()返回的是第一个符合条件的值;findIndex()返回的是第一个返回条件的值的索引值 |
reduce() 和 reduceRight() | 否 | 数组方法,reduce()对数组正序操作;reduceRight()对数组逆序操作 |
forEach和map有什么区别
相同点
- 只能遍历数组
- 有相同的三个值:currentValue(当前元素的值,必选),index(当前元素的下标,可选),arr(当前遍历的数组对象,可选)
- 匿名函数中的this都是指向 window
- 相当于封装好的单层 **for **循环
区别
- forEach() 方法没有返回值
- map() 方法有返回值,返回一个新数组,新数组中的值为原数组调用函数处理之后的值
- forEach() 遍历通常都是直接引入当前遍历数组的内存地址,生成的数组的值发生变化,当前遍历的数组对应的值也会发生变化
- map() 遍历后的数组通常都是生成一个新的数组,新的数组的值发生变化,当前遍历的数组的值不会变
总结
为什么都说遍历后通常是引入当前遍历数组的内存地址和生成一个新的数组,因为按 forEach() 和 map() 创作思想,forEach() 遍历基本引入遍历数组内存地址、**map() **遍历而是生成一个新的数组,但是有些人写的时候不按 map() 和 forEach() 的常规语法来,导致使用 map() 遍历数组时,返回的值是直接把当前遍历数组的每个元素的内存地址给了另外一个数组,本质还是引用遍历数组中每个元素的内存地址,这样生成的数组已经不能称作为一个新的数组同样也把 **map() **最大的一个特性给舍弃了,同理如果 map() 和 forEach() 本质没区别的话,没必要把他们封装成两个函数,封装成一个就好了
let arr = [
{
title:'雪碧',
price: 2.5,
},
{
title:'可乐',
price: 2.5,
}
]
let a = arr.forEach((item,index) =>{
return item
})
let b = arr.map((item,index) =>{
return item
})
console.log(arr) //打印arr数组
console.log(a) //undefined
console.log(b) //打印arr数组
// map()方法是有返回值的,而forEach()方法没有返回值
// 但是如果用map()方法想让b获取arr的数组的值,不建议这样的写法,因为上面我们已经说到了map()方法主要是生成一个新的数组,而不是直接引入arr数组内存地址
let arr = [{title:'雪碧',price: 2.5},{title:'可乐',price: 2.5}]
let list = [{title:'雪碧',price: 2.5},{title:'可乐',price: 2.5}]
let a = [];
let b = [];
arr.forEach((item,index) =>{
a[index] = item;
})
b = list.map((item,index) =>{
return item
})
// 第一次打印
// 到这里我们可以看到,a和b都已经成功的接收了arr和list的数组的数据,
// 强调map()一定不要直接return item,这里这么写主要是为了区分生成新数组和内存地址的区别
console.log(a);
console.log(b);
// 第二次打印
a[0].price = 3;
b[0].price = 3;
console.log(a);
console.log(b);
console.log(arr)
console.log(list)
第一次打印
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tq4pVlsW-1660817203339)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ae8fdfceaced446cacccb0b304213fd5~tplv-k3u1fbpfcp-zoom-1.image)]
第二次打印
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-funPUIYO-1660817203339)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/69bb384523484976a33e27d1062ddf9b~tplv-k3u1fbpfcp-zoom-1.image)]
:::info
从 案例2 这里我们看到只更改了 **a **和 **b **中 price 值,但是 **arr **和 **list **数组 **price 的值也发生了变化,arr 发生变化属于正常现象,因为在上述中已经说过 forEach() 方法遍历通常都是引入遍历数组的内存地址,不管是arr **发生改变还是 **a **发生改变,其实都是直接改变同一个内存地址,所以他们的值会一直同步,但是 **map() **方法生成的是一个新的数组,为什么 **b **的值发生改变,list 值同样发生改变呢,这里就是上述说的内存地址引入问题
:::
let arr = [{title:'雪碧',price: 2.5},{title:'可乐',price: 2.5}]
let list = [{title:'雪碧',price: 2.5},{title:'可乐',price: 2.5}]
let a = [];
let b = [];
arr.forEach((item,index) =>{
a[index] = item;
})
b = list.map((item,index) =>{
return{
title:item.title,
price:item.price
}
})
b[0].price = 3;
console.log(b);
console.log(list)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zFbcELUt-1660817203340)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/74a73faa03a84b9da60c5f207b163f9f~tplv-k3u1fbpfcp-zoom-1.image)]
在 案例3这里我们改变了一下写法,就会发现在改变 b 值,list就不会发生变化了。
在 案例2 中改变 b的值,list的值发现变化是因为 map() 遍历后生成的是一个新的数组,但是在遍历的过程还是引入旧数组的内容地址,而在 案例3 中我们通过 map() 遍历的时候自己定义一下想要参数名,只复制一下旧数组的值,遍历完后会生成新的内存空间去存储 b的值,所以我们在改变 b的值时候也只是改变了 b内存中的值,而没有改变 list内存的值,同样的在改变 list的值,b的值也不会发现改变,所以说 map() 方法遍历后会生成一个新的数组
三、对象
1.浅拷贝
ES6 中也可以通过 …扩展运算符复制一个对象
const obj = { name: 'dengke' }
const obj1 = {
age: 18,
temp: {
a: 10
}
}
const obj2 = { ...obj, ...obj1 }
console.log(obj2) // { name: 'dengke', age: 18, temp: { a: 10 } }
obj2.temp.a = 20
console.log(obj2) // { name: 'dengke', age: 18, temp: { a: 20 } }
console.log(obj1) // { name: 'dengke', age: 18, temp: { a: 20 } }
2.访问对象属性
以 **. **的方式访问
// 对象的创建并添加属性
const obj = {
name: 'pengyuyan',
address: {
a: '山东省,
b: 266000
},
arr: [1, 2],
sayHelllo: (name) => {
console.log(`hello,${name}`)
}
}
// 用 dot(点 .) 的方式访问
console.log(obj.name) // pengyuyan
console.log(obj.address) // {"a":'山东省',"b":266000}
console.log(obj.arr) // [1,2]
obj.sayHelllo('huge') // hello,huge
以 [ ] 形式访问
// 用 [] 的方式访问
console.log(obj['name']) // pengyuyan
console.log(obj['age']) // {"a":'266000',"b":266000}
console.log(obj['arr']) // [1,2]
obj['sayHelllo']('huge') // hello,huge
区别
[ ] 语法可以用变量作为属性名或访问,而 …语法不可以
const obj = {};
obj.name = 'pengyuyan';
const myName = 'name';
console.log(obj.myName);// undefined,访问不到对应的属性
console.log(obj[myName]);// pengyuyan
const person = {
name:'huge'
};
console.log(person["name"]);//huge
console.log(person.name); //huge
// 可以通过变量来访问属性
const propertyName = 'name';
console.log(person[propertyName]); //huge
var propertyName2 = 'name2';
console.log(person[propertyName2]); //undefined
[ ]语法可以用数字作为属性名,而… 语法不可以
const obj1 = {};
obj1.1 = 1; // Unexpected number
obj1[2] = 2;
console.log(obj1[2]);//2
console.log(obj1)//{2: 2}
3.枚举对象的属性
for … in …
会遍历对象中所有的可枚举属性(包括自有属性和继承属性)
const obj = {
a: '山东省',
b: 266000
}
// 使用Object.create创建一个原型为obj的对象 (模拟继承来的属性)
const newObj = Object.create(obj)
newObj.newA = '山西省'
newObj.newB = '030001'
for(i in newObj){
console.log(i)
}
// newA
// newB
// a
// b
// 将其中的一个属性变为不可枚举属性
Object.defineProperty(newObj, 'newA', {
enumerable: false
})
for(i in newObj){
console.log(i)
}
// newB
// a
// b
Object.keys()
返回一个包括所有的可枚举的自有属性的名称组成的数组
// 接 for... in... 的例子
const result = Object.keys(newObj)
console.log(result) // ["newB"]
Object.getOwnPropertyNames()
- 返回一个包括自有属性的名称 (不管是不是可枚举的)的数组,该数组是由 obj 自身的可枚举和不可枚举属性的名称组成。
- 数组中枚举属性的顺序与通过 for…in循环 Object.keys() 迭代该对象属性时一致。
// 接 for... in... 的例子
const result = Object.getOwnPropertyNames(newObj)
console.log(result) // ['newA','newB']
Object.getOwnPropertyNames()和Object.keys()的区别
- Object.keys() 只适用于可枚举的属性
- Object.getOwnPropertyNames() 返回对象的全部属性名称(包括不可枚举的)。
4.Object.assign()
用于将所有可枚举属性的值从一个或多个源对象分配到目标对象,它将返回目标对象。
常用来合并对象
Object.assign(target, ...sources)
// 参数:target 目标参数,sources源对象 返回值:目标对象
const obj1 = { a: 1, b: 2 }
const obj2 = { b: 3, c: 4 }
const obj3 = Object.assign(obj1, obj2)
const obj4 = Object.assign({}, obj1) // 克隆了obj1对象
console.log(obj1) // { a: 1, b: 3, c: 4 } 对同名属性b进行了替换 obj1发生改变是因为obj2赋给了obj1
console.log(obj2) // { b: 3, c: 4 }
console.log(obj3) // { a: 1, b: 3, c: 4 }
console.log(obj4) // { a: 1, b: 3, c: 4 }
注意:
- 如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖。
- Object.assign() 方法只会拷贝源对象自身的并且可枚举的属性到目标对象。
- 它是浅拷贝,也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用,同名属性会替换
- Object.assign() 不会在那些 source对象值为 null或 undefined的时候抛出错误
const obj1 = { a: 1, b: 2 }
const obj5 = {
name: 'pengyuyan',
a: '你好',
fn: {
sum: 10
}
}
const obj6 = Object.assign(obj1, obj5)
console.log(obj6) // { a: '你好', b: 2, fn: {sum: 10},name: 'pengyuyan'}
console.log(obj1) // {a: '你好', b: 2, fn: {sum: 10},name: 'pengyuyan'} 对同名属性a进行了替换
5.Object.values()
语法:Object.values(obj)
- 参数:**obj **被返回可枚举属性值的对象
- 返回值:一个包含对象自身的所有可枚举属性值的数组,值的顺序与使用for…in循环的顺序相同 ( 区别在于 for-in 循环枚举原型链中的属性 )。与Object.keys() 相似,区别在于这个返回的是数据的值是value
const obj = { name: 'pengyuyan', age: 18 }
console.log(Object.values(obj)) // ['pengyuyan', 18]
const obj1 = { 0: 'a', 1: 'b', 2: 'c' }
console.log(Object.values(obj1)) // ['a', 'b', 'c']
// 如果对象key为number的话,会从升序枚举返回
const obj2 = { 10: 'a', 1: 'b', 2: 'c' }
console.log(Object.values(obj2)) // ['b', 'c', 'a']
6.Object.prototype.hasOwnProperty()
语法:obj.hasOwnProperty(prop)
- 该方法只会对自身属性进行判断,查询自身属性中是否具有指定的属性,会返回一个布尔值
- 继承来的属性一律返回false
- 可配合for…in使用,可以避免其遍历继承来的属性
const obj = new Object();
obj.property = 'pengyuyan'
// 只要属性存在,也返回true
obj.property2 = null
obj.property3 = undefined
Object.prototype.property4 = 0
console.log(obj.hasOwnProperty('property')) // true
console.log(obj.hasOwnProperty('property2')) // true
console.log(obj.hasOwnProperty('property3')) // true
console.log(obj.hasOwnProperty('property3')) // true
三、操作符
1.==
对于 == 来说,如果对比双方的类型不一样,就会进行类型转换。
- console.log(“1” == 1) // true
- console.log(“1” === 1 ) // false === 不会类型转换,会首先判断两个值的类型是否相同,如果相同再进行比较
- object == string,先接将 **object **类型转换为 **string **类型后再进行比较
- **object **== (number/boolean),先将 object类型转换为 string类型,再将 string类型转换为 number类型,和另一个值比较,如果另一个值不是 number类型先将其转换为 number类型
- 其他比较:**number **== boolean,**string **== boolean,将不是 **number **类型的值转换为 **number **类型再做比较。
:::
console.log(null == undefined) // true
console.log(null === undefined) // false
// *注意: null 或者 undefined 和其他任何的数据类型比较都返回 false
[1,2] == '1,2' //true,[1,2].toString() ==> "1,2"
(function(){console.log('hello')}) == "function(){console.log('hello')}" //true
[1] == true //true,[1].toString() ==> "1",Number("1") ==> 1,Number(true) ==> 1
[12] == 12 //true,[12].toString() ==> "12",Number("12") ==> 12
流程图如下:
2.|| 和 &&
|| 和 && 首先会对第一个操作数执行条件判断,如果其不是布尔值就先强制转换为布尔类型,然后再执行条件判断。
- 对于 || 来说,如果条件判断结果为 true就返回第一个操作数的值,如果为 false就返回第二个操作数的值
- && 则相反,如果条件判断结果为 true就返回第二个操作数的值,如果为 false就返回第一个操作数的值
- || 和 && 返回它们其中一个操作数的值,而非条件判断的结果
四、深浅拷贝
1.浅拷贝
扩展运算符
let outObj = {inObj: {a: 1, b: 2} }
let newObj = {...outObj}
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}
对象扩展运算符
对象的扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中。
let bar = { a: 1, b: 2 }
let baz = { ...bar }; // { a: 1, b: 2 }
上述方法实际上等价于:
let bar = { a: 1, b: 2 }
let baz = Object.assign({}, bar) // { a: 1, b: 2 }
let bar = {a: 1, b: 2}
let baz = {...bar, ...{a:2, b: 4}} // {a: 2, b: 4}
利用上述特性就可以很方便的修改对象的部分属性。在redux中的reducer函数规定必须是一个纯函数,reducer中的state对象要求不能直接修改,可以通过扩展运算符把修改路径的对象都复制一遍,然后产生一个新的对象返回。
数组扩展运算符
数组的扩展运算符可以将一个数组转为用逗号分隔的参数序列,且每次只能展开一层数组。
console.log(...[1, 2, 3]) // 1 2 3
console.log(...[1, [2, 3, 4], 5]) // 1 [2, 3, 4] 5
下面是数组的扩展运算符的应用:
- 将数组转换为参数序列
function add(x, y) {
return x + y
}
const numbers = [1, 2]
add(...numbers) // 3
- 复制数组
const arr1 = [1, 2]
const arr2 = [...arr1]
要记住:扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中,这里参数对象是个数组,数组里面的所有对象都是基础数据类型,将所有基础数据类型重新拷贝到新的数组中。
- 合并数组
如果想在数组内合并数组,可以这样:
const arr1 = ['two', 'three']
const arr2 = ['one', ...arr1, 'four', 'five'] // ["one", "two", "three", "four", "five"]
- 扩展运算符与解构赋值结合起来,用于生成数组
const [first, ...rest] = [1, 2, 3, 4, 5];
console.log(first) // 1
console.log(rest) // [2, 3, 4, 5]
需要注意:如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
const [...rest, last] = [1, 2, 3, 4, 5] // 报错
const [first, ...rest, last] = [1, 2, 3, 4, 5] // 报错
- 将字符串转为真正的数组
[...'hello'] // [ "h", "e", "l", "l", "o" ]
- 任何 Iterator 接口的对象,都可以用扩展运算符转为真正的数组
比较常见的应用是可以将某些数据结构转为数组:
// arguments对象
function foo() {
const args = [...arguments]
}
用于替换es5中的**Array.prototype.slice.call(arguments)**写法。
- 使用Math函数获取数组中特定的值
const numbers = [9, 4, 7, 1]
Math.min(...numbers) // 1
Math.max(...numbers); // 9
Object.assign()
:::info
用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)
详细查看
:::
let outObj = { inObj: {a: 1, b: 2} }
let newObj = Object.assign({}, outObj)
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}
区别
- Object.assign() 方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target),接收的第一个参数作为目标对象,后面的所有参数作为源对象。然后把所有的源对象合并到目标对象中。它会修改了一个对象,因此会触发 ES6 setter,(如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性)
- 扩展操作符(…)使用它时,数组或对象中的每一个值都会被拷贝到一个新的数组或对象中。它不复制继承的属性或类的属性,但是它会复制** ES6 **的 symbols属性。同样,如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。
2.深拷贝
为什么使用深拷贝?
- 我们希望在改变新的数组(对象)的时候,不改变原数组(对象)
- 深拷贝就是能够实现真正意义上的数组和对象的拷贝。(深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象)
JSON.stringify()以及JSON.parse()(简单深拷贝 )
const obj = {
a: 1,
b: 2,
c: 3
}
const copyObj = JSON.parse(JSON.stringify(obj))
copyObj.a = 5;
console.log(obj.a); // 1
console.log(copyObj.a); // 5 修改copyObj的数据,并不会对obj造成任何影响
// 注意:JSON.stringify()以及JSON.parse()它是不可以拷贝 undefined , function, RegExp 等等类型的
// 不能拷贝函数
递归
function deepClone1(obj) {
//判断拷贝的要进行深拷贝的是数组还是对象,是数组的话进行数组拷贝,对象的话进行对象拷贝
const objClone = Array.isArray(obj) ? [] : {};
//进行深拷贝的不能为空,并且是对象或者是
if (obj && typeof obj === "object") {
for (key in obj) {
if (obj.hasOwnProperty(key)) {
if (obj[key] && typeof obj[key] === "object") {
objClone[key] = deepClone1(obj[key]);
} else {
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
concat()
// 当数组中都是简单数据类型时
let arr = [1, 2, false, 'a']
let arr2 = [].concat(arr)
arr[0] = 3
console.log(arr) // [3, 2, false, 'a'] arr中的数据更改,并不会影响arr2
console.log(arr2) // [1, 2, false, 'a']
// 如果数组中有复杂数据类型
let arr = [1, 2, false, {a: 3}]
let arr2 = [].concat(arr)
arr[3].a = 4
console.log(arr) // [1, 2, false, {a: 4}] arr中的数据更改,arr2中的数据会跟着变
console.log(arr2) // [1, 2, false, {a: 4}]