theme: channing-cyan

一、数据类型

1. JavaScript共有八种数据类型

其中 SymbolBigIntES6中新增的数据类型:

  • Symbol代表创建后独一无二且不可变的数据类型,它主要是为了解决可能出现的全局变量冲突的问题。
  • BigInt 是一种数字类型的数据,它可以表示任意精度格式的整数,使用 BigInt可以安全地存储和操作大整数,即使这个数已经超出了 Number能够表示的安全整数范围。

这些数据可以分为原始数据类型和引用数据类型:

  • 栈:原始数据类型(UndefinedNullBooleanNumberString
  • 堆:引用数据类型(对象数组函数

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

可以看到:

  1. instanceof 只能正确判断 引用 数据类型,而不能判断 基本 数据类型。
  2. 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有两个作用:

  1. 判断数据的类型
  2. 对象实例通过 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) 的结果不一样,这是为什么?
这是因为 toStringObject 原型方法,而 Arrayfunction 等类型作为 Object 的实例,都重写了 toString 方法。不同的对象类型调用 toString 方法时,根据原型链的知识,调用的是对应的重写之后的 toString() 方法( function类型返回内容为函数体的字符串Array类型返回元素组成的字符串…),而不会去调用 Object上原型 toString() 方法 (返回对象的具体类型),所以采用 obj.toString() 不能得到其对象类型,只能将 **obj **转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用 Object 原型上的 toString() 方法。

4.null和undefined区别

  1. 首先 undefinedNull 都是 基本 数据类型,这两个基本数据类型分别都只有一个值,就是 undefinednull
  2. undefined 代表的含义是 未定义null 代表的含义是 空对象
  3. 一般变量声明了但还没有定义的时候会返回 undefined
  4. null主要用于赋值给一些可能会返回对象的变量
  5. 作为初始化。undefinedJavaScript中不是一个保留字,这意味着可以使用 **undefined **来作为一个变量名,但是这样的做法是非常危险的,它会影响对 undefined值的判断。我们可以通过一些方法获得安全的 undefined 值,比如说 void 0
  6. 当对这两种类型使用 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 !== NaNtrue
:::

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
结论:如果没有提供initialValuereduce 会从索引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有什么区别

相同点

  1. 只能遍历数组
  2. 有相同的三个值:currentValue(当前元素的值,必选),index(当前元素的下标,可选),arr(当前遍历的数组对象,可选)
  3. 匿名函数中的this都是指向 window
  4. 相当于封装好的单层 **for **循环

区别

  1. forEach() 方法没有返回值
  2. map() 方法有返回值,返回一个新数组,新数组中的值为原数组调用函数处理之后的值
  3. forEach() 遍历通常都是直接引入当前遍历数组的内存地址,生成的数组的值发生变化,当前遍历的数组对应的值也会发生变化
  4. 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) //undefinedconsole.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]);// pengyuyanconst person = {name:'huge'};console.log(person["name"]);//hugeconsole.log(person.name); //huge// 可以通过变量来访问属性const propertyName = 'name';console.log(person[propertyName]);  //hugevar propertyName2 = 'name2';console.log(person[propertyName2]);  //undefined

[ ]语法可以用数字作为属性名,而… 语法不可以

const obj1 = {};
obj1.1 = 1; // Unexpected numberobj1[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()

  1. 返回一个包括自有属性名称 (不管是不是可枚举的)的数组,该数组是由 obj 自身的可枚举不可枚举属性的名称组成
  2. 数组中枚举属性的顺序与通过 for…in循环 Object.keys() 迭代该对象属性时一致。
//  接 for... in... 的例子
const result = Object.getOwnPropertyNames(newObj)
console.log(result) // ['newA','newB']

Object.getOwnPropertyNames()和Object.keys()的区别

  1. Object.keys() 只适用于可枚举的属性
  2. 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 }

注意:

  1. 如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖
  2. Object.assign() 方法只会拷贝源对象自身的并且可枚举的属性到目标对象。
  3. 它是浅拷贝,也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用,同名属性会替换
  4. Object.assign() 不会在那些 source对象值为 nullundefined的时候抛出错误
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)

  1. 参数:**obj **被返回可枚举属性值的对象
  2. 返回值:一个包含对象自身的所有可枚举属性值的数组,值的顺序与使用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)

  1. 该方法只会对自身属性进行判断,查询自身属性中是否具有指定的属性,会返回一个布尔值
  2. 继承来的属性一律返回false
  3. 可配合for…in使用,可以避免其遍历继承来的属性
const obj = new Object();
obj.property = 'pengyuyan'// 只要属性存在,也返回true
obj.property2 = null
obj.property3 = undefinedObject.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 === 不会类型转换,会首先判断两个值的类型是否相同,如果相同再进行比较
  1. object == string,先接将 **object **类型转换为 **string **类型后再进行比较
  2. **object **== (number/boolean),先将 object类型转换为 string类型,再将 string类型转换为 number类型,和另一个值比较,如果另一个值不是 number类型先将其转换为 number类型
  3. 其他比较:**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.|| 和 &&

||&& 首先会对第一个操作数执行条件判断,如果其不是布尔值就先强制转换为布尔类型,然后再执行条件判断。

  1. 对于 || 来说,如果条件判断结果为 true就返回第一个操作数的值,如果为 false就返回第二个操作数的值
  2. && 则相反,如果条件判断结果为 true就返回第二个操作数的值,如果为 false就返回第一个操作数的值
  3. ||&& 返回它们其中一个操作数的值,而非条件判断的结果

四、深浅拷贝

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}}

区别

  1. Object.assign() 方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target),接收的第一个参数作为目标对象,后面的所有参数作为源对象。然后把所有的源对象合并到目标对象中。它会修改了一个对象,因此会触发 ES6 setter,(如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性)
  2. 扩展操作符)使用它时,数组或对象中的每一个值都会被拷贝到一个新的数组或对象中。它不复制继承的属性或类的属性,但是它会复制** ES6 **的 symbols属性。同样,如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。

2.深拷贝

为什么使用深拷贝?

  1. 我们希望在改变新的数组(对象)的时候,不改变原数组(对象)
  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}]

JavaScript (上篇)相关推荐

  1. 前端猎奇系列之探索Python来反补JavaScript——上篇

    写在最前 人生苦短,我用 JavaScript. 然鹅,其他圈子里还流行这样一句话:人生苦短,我用 Python. 当然还有什么完美秀发编程,这就不提了.当你在前端学到一定地步的时候,你会有种想出去看 ...

  2. html打包成app的缓存问题,webpack 独立打包与缓存处理

    关于 微信公众号:前端呼啦圈(Love-FED) 个人博客:劳卜的博客 知乎专栏:前端呼啦圈 前言 先前写了一篇webpack入门的文章<webpack入门必知必会>,简单介绍了webpa ...

  3. Javascript笔记:(实践篇)从jQuery插件技术说起(上篇)

    最近写了个网站,当时借鉴了很多相关网站前端技术,为了让客户的体验更加好,我在网站前端加入了相当多的校验代码,因此代码显的特别臃肿.虽然开发过程中我将前端代码重构了三次,但是我还是对我原来写的代码不满意 ...

  4. 【JavaScript 进阶教程】“原型“与“原型链“【上篇】

    文章目录: 一:构造函数 1.1 构造函数使用方法 1.2 构造函数new的执行过程 new 执行过程 1.3 实例成员与静态成员 二: 原型对象 prototype 2.1 为什么有原型对象 2.2 ...

  5. javascript 基础篇2 数据类型,语句,函数

    文章里如果有错误的话,希望能帮忙指正~我也是边看视频边学习中,这个算是个笔记吧~自认为总结出来的东西比看视频要节省点时间~能帮到别人最好了~帮不到也起码恩能帮到我自己 嘿~ 写内容之前废话一句:因为旧 ...

  6. TypeScript 从听说到入门(上篇)

    我为什么会这样念念又不忘 / 你用什么牌的箭刺穿我心脏 我也久经沙场 / 戎马生涯 / 依然 / 被一箭刺伤 --李荣浩<念念又不忘> 接下来我会分上.下两篇文章介绍 TypeScript ...

  7. OOJ-面向对象的JAVASCRIPT(二)

    本文继上篇文章介绍javascript 匿名函数以及闭包的特性 1.什么叫匿名函数? 匿名函数:就是没有函数名的函数. 函数是JavaScript中最灵活的一种对象,这里只是讲解其匿名函数的用途. 1 ...

  8. JavaScript深入之变量对象

    JavaScript深入系列第四篇,具体讲解执行上下文中的变量对象与活动对象.全局上下文下的变量对象是什么?函数上下文下的活动对象是如何分析和执行的?还有两个思考题帮你加深印象,快来看看吧! 前言 在 ...

  9. JavaScript深入之执行上下文栈

    顺序执行? 如果要问到 JavaScript 代码执行顺序的话,想必写过 JavaScript 的开发者都会有个直观的印象,那就是顺序执行,毕竟: var foo = function () {con ...

最新文章

  1. autocad2007二维图画法_cad怎样绘制简单的二维图形
  2. hive的变量传递设置
  3. python画二维数组散点图_Python散点图二维数组
  4. java实现apriori算法_七大经典、常用排序算法的原理、Java 实现以及算法分析
  5. python123文件和数据格式化测试7_二级Python----文件和数据格式化(DAY 7)
  6. 最简单的网络图片的爬取 --Pyhon网络爬虫与信息获取
  7. System.Runtime.InteropServices.Automation
  8. adsl服务器客户端配置cisco_【干货】Cisco路由排错经典案例分析
  9. ios html5 文件上传,【Web前端问题】上传文件使用axios发送FormData数据,参数为空...
  10. bartender实现即扫即打印功能扫描完后自动打印_日本彩色激光打印机推荐人气排名15款...
  11. rms 公式 有效值_有效值是电流电压的均方根值吗?
  12. Tsinsen A1517. 动态树 树链剖分,线段树,子树操作
  13. python cad自动画图软件_科研画图都用什么软件?
  14. 计算机三维建模方法,三维建模知识介绍
  15. 二维向量的叉乘判断凹凸多边形
  16. 聊聊泰国的工作和生活
  17. 1. Cloudcraft
  18. golang命令行贪吃蛇
  19. JAVASE篇的入门经典书籍推荐
  20. 手机PDF转换器如何实现PPT转换PDF格式

热门文章

  1. Aura Components Basics on Trailhead —— Input Data Using Forms
  2. keil rt-thread link.sct 解析
  3. android hero动画,主动画 (Hero animations)
  4. java 计算 点是否在电子围栏范围内
  5. java计算机毕业设计基于ssm的基于android的家庭理财系统
  6. OkHttpUtils | okhttp-OkGo的使用,完美支持RxJava
  7. Baklib知识管理体系:将知识管理深化到企业中
  8. jquery :visible Selector 用于判断元素是否显示
  9. 超声波测距仪编程_简易超声波测距仪的制作
  10. 在C或C++中如何使用PI(π)值