1.js数据类型:
https://blog.csdn.net/lareinalove/article/details/79895760
基本数据类型 | 引用数据类型 | |
---|---|---|
有哪些 | string、number、boolean、undefined、null、symbol | Object、Array、Function、RegExp、Date、特殊的基本包装类型(String、Number、Boolean)以及单体内置对象(Global、Math)。 |
访问方式 | 按值访问的 | 变量指向的是数据的地址 |
内存分配 | 存储在栈中,栈里面直接开辟一个空间存储变量的值。 | 堆中存放数据,栈中存放这个数据的地址 |
值的可变性 | 基本数据类型的值是不可变的(在字符串的不可变性上有充分体现)。不可以添加属性和方法。 | 引用数据类型可以添加属性和方法 |
赋值 | var a=3;var b=a;基本数据类型的赋值会开辟一块新的空间,然后改变变量的指针指向 | var obj={};var obj2=obj;仅改变引用的指针,指向的是同一个对象 |
调用函数时传递的参数 | 因为传递的是‘值’,所以在进入函数内部时,会开辟一个新的变量和空间来存储这个‘值’。此时修改值,由于是不同的内存,所以不会影响原来的实参。 | 因为传递的是‘地址,所以在进入函数内部时,会开辟一个新的变量和空间来存储这个‘地址’。由于地址相同,即指向的是堆里面同样的数据,所以修改的时候会影响之前的实参。 |
js中有三种原始类型的值:string,number,boolean。
通过包装类型,即三个原生对象Number、String、Boolean可以将这三种原始类型的值转换为包装对象。
这样就可以使对象这种类型覆盖js中所有的值。
当这三个原生对象作为构造函数使用时,可以将原始类型的值转为对象。
这三个原生对象作为普通函数使用时,可以将任意类型的值转换为原始类型的值。
2.关于NaN:
全称为not a number,但是!!!!typeof NaN 的值为number
NaN
是一个全局对象的属性,NaN 属性的初始值就是 NaN。
NaN与一切为敌(包括自己),与之相比都是false。
isNaN()
与Number.isNaN()
的区别:
isNaN('hello world'); // true 会先将内部的字符串转换为number
Number.isNaN('hello world'); // false 不存在转换,直接比较
3.深浅拷贝
(1)可枚举与不可枚举:
可枚举和不可枚举,是由属性的enumerable值决定的。取值为true,表示可枚举。否则表示不可枚举。
for…in只能遍历出可枚举的属性。
Object.keys返回的也是可枚举的属性。
var obj = {
name: "xiaoming",
age: 12,
sex: "男",
};
Object.defineProperty(obj, "color1", {
value: "red",
writable: true,
enumerable: false,
configurable: false,
});
Object.defineProperty(obj, "color2", {
value: "green",
writable: true, //是否可以重写,默认值为false
enumerable: true, //是否可以通过Object.keys()遍历,默认为false
configurable: false //是否可以删除该属性以及修改enumerable和configurable,默认为false
});
console.log(Object.keys(obj)); //["name", "age", "sex", "color2"]
// 通过Object.keys可以获取一个对象含有的可枚举属性的个数
console.log(Object.keys(obj).length); //4
for (k in obj) {
console.log(k); //name age sex color2
}
// 判断一个属性是否是可枚举的
console.log(obj.propertyIsEnumerable("color1")); //false
console.log(obj.propertyIsEnumerable("length")); //false
console.log(obj.propertyIsEnumerable("color2")); //true
var arr = ["apple", "banana", "orange"];
// Object.keys只返回可枚举的属性,Object.getOwnPropertyNames还会返回不可枚举的属性
console.log(Object.keys(arr).length); //3
console.log(Object.getOwnPropertyNames(arr)); //["0", "1", "2", "length"]
console.log(Object.getOwnPropertyNames(obj)); //["name", "age", "sex", "color1", "color2"]
console.log(Object.getOwnPropertyNames(arr).length); //4
(2)赋值,浅拷贝,深拷贝:
var obj1 = {
'name' : 'zhangsan',
'age' : '18',
'language' : [1,[2,3],[4,5]],
};
//赋值:相当于地址的直接引用
var obj2 = obj1;
//浅拷贝:浅拷贝只会拷贝最外面的一层,里面的子对象拷贝的是地址,即引用,修改时会连着修改
var obj3 = shallowCopy(obj1);
function shallowCopy(src) {
var dst = {};
for (var prop in src) {
if (src.hasOwnProperty(prop)) {
dst[prop] = src[prop];
}
}
return dst;
}
//深拷贝:每一层的数据都会被拷贝,开辟的都是新的空间。
(3)浅拷贝的实现(4种):
1.传统方式:使用for…in遍历对象的最外层
var shallowCopy = function(obj) {
// 只拷贝对象
if (typeof obj !== 'object') return;
// 根据obj的类型判断是新建一个数组还是对象
var newObj = obj instanceof Array ? [] : {};
// 遍历obj,并且判断是obj的属性才拷贝
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key];
}
}
return newObj;
}
2.Object.assign()
ES6中的Object.assign()用于将源对象的属性复制到目标对象,该方法使用的是浅拷贝的方式。
(该方法针对的是所有对象,包括了数组)
//对象的浅拷贝
var obj1 = {
name: "tim",
msg: {
age: 29,
},
};
var obj4=Object.assign({}, obj1);
obj4.msg.age = 30;
obj4.name = "alice";
console.log(obj1); //tim 30
//数组的浅拷贝
let arr1 = [1, 2, { name: "liu" }];
let arr2 = Object.assign([], arr1);
console.log(arr2); //[1, 2, {name: "liu"}]
arr2[2].name = "xiao";
console.log(arr1); [1, 2, {name: "xiao"}]
扩展:Object.assign(obj1,obj2)//obj2的属性会覆盖掉obj1中重复的属性
3.利用数组的silce,concat方法
对于数组的浅拷贝,还可以通过数组的slice和concat方法返回一个新的数组的方式来实现,这些方法内部实现的是浅拷贝。
let arr3 = [1, 2, { name: "liu", likes: ["music", "movie"] }];
let arr4 = arr3.slice(0);
console.log(arr4); //[1, 2, { name: "liu", likes: ["music", "movie"] }]
arr4[2].name = "xiao";
console.log(arr3); //[1, 2, { name: "xiao", likes: ["music", "movie"] }]
let arr5 = [1, 2, { name: "liu", likes: ["music", "movie"] }];
let arr6 = arr5.concat([]);
console.log(arr6);
arr6[2].likes[0] = "drink"; //[1, 2, { name: "liu", likes: ["music", "movie"] }]
console.log(arr5); //[1, 2, { name: "liu", likes: ["drink", "movie"] }]
4.数组的浅拷贝还可以通过…展开符来实现
let arr3 = [1, 2, { name: "title" }];
let arr4 = new Array(...arr3);
arr4[0] = "aaa";
arr4[2].name = "xiao";
console.log(arr3); //[1, 2, { name: "xiao" }]
(4)深拷贝的实现(2种):
1.递归实现深度拷贝:(只能实现对象和数组的克隆)
// 深拷贝:每一层的数据都会被拷贝,开辟的都是新的空间。
var obj = {
name: "tim",
color: ["white", "black"],
size: {
content: "small",
},
};
// 以下为通过函数递归的方式实现深拷贝
function deepCopy(newObj, oldObj) {
for (k in oldObj) {
// 判断是不是数组
if (oldObj[k] instanceof Array) {
newObj[k] = [];
deepCopy(newObj[k], oldObj[k]);
// 判断是不是对象
// ❤ 注意:需要先判断是不是数组,再判断是不是对象,因为数组也属于对象
} else if (oldObj[k] instanceof Object) {
newObj[k] = {};
deepCopy(newObj[k], oldObj[k]);
} else {
newObj[k] = oldObj[k];
}
}
}
var obj2 = {};
deepCopy(obj2, obj);
console.log(obj2);
obj2.color[0] = "yellow"; //深拷贝,不会改变原来的值
console.log(obj.color); //["white", "black"]
//简写方式
var deepCopy = function(obj) {
if (typeof obj !== 'object') return;
var newObj = obj instanceof Array ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
//如果属性是对象,则需要进一步递归
newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
}
}
return newObj;
}
2.借助JSON对象的转换:
原理:JOSN对象中的stringify可以把一个js对象序列化为一个JSON字符串,parse可以把JSON字符串反序列化为一个js对象,通过这两个方法,也可以实现对象的深拷贝。
//对象的深拷贝
let obj1 = { a: 0 , b: { c: 0}};
let obj3 = JSON.parse(JSON.stringify(obj1));
obj3.b.c = 4;
console.log(obj1);//{ a: 0 , b: { c: 0}} 未改变
//数组的深拷贝
var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}]
var arr2 = JSON.parse( JSON.stringify(arr) );
arr2[3][0]='new1';
console.log(arr);// ['old', 1, true, ['old1', 'old2'], {old: 1}] 未改变
注意:该方法不能拷贝函数对象,还会有很多小问题。
3.包装对象Number,String,Boolean,以及正则对象RegExp和Date对象的克隆
其实谈不上克隆一说,包装对象直接通过=赋值,正则对象和Date对象直接在原基础上通过new创建新的对象
4.比较判断相关
(1)隐式转换:
https://blog.csdn.net/itcast_cn/article/details/82887895
字符串连接符+的隐式转换:
当+两侧有一个是字符串时,表明这个+是字符串连接符。此时会将其他数据类型调用toString()转换为字符串再拼接。
console.log(1+"true")//1true
算术运算符+的隐式转换:
当+两侧没有字符串时,表明这个+号是算术运算符。此时会把其他数据类型调用Number()转成数字后再做加法运算。
console.log(1+true)//会转换为1+1=2
console.log(1+undefined)//转换为1+Number(undefined)=1+NaN =NaN
console.log(1+null)//1+0=1
关系运算符> < ==的隐式转换:
当关系运算符两边只有一边是字符串时,该字符串会通过Number()转换为数字再进行比较
console.log("2">10)//false
当关系运算符两边都是字符串时,会按照字符串从左到右转换为unicode编码依次进行比较。
console.log("2">"10")//"2".charCodeAt()>"10".charCodeAt() 50>49 true
"2".charCodeAt()//50
"10".charCodeAt()//49
特殊情况:(无规则)
console.log(undefined==undefined);//true
console.log(undefined==null);//true
console.log(null==null);//true
console.log(NaN==NaN);//false NaN不与任何数据相等,包括自己
复杂数据类型的隐式转换:
转换规则:对于对象和数组的隐式转换,会先通过valueOf()获取原始值,然后通过toString()转换为字符串。然后再通过Number()转换为数字。
注意:[].toString()为空字符串,{}.toString()为[object Object]
console.log([1,2]=='1,2')//true
//[1,2].valueOf().toString()='1,2'
let a={};
console.log(a=="[object Object]")//true
//{}.valueOf().toString()等于[object Object]
应用:if(a1&&a2&&a==3){
console.log(1)
}
如何完善a,使其正确打印1:
let a=??;
if(a==1&&a==2&&a==3){
console.log(1)
}
解决方法:重写a属性的valueOf()方法
a={
i:0;
valueOf:function(){
return ++a.i;//每调用一次,就让a对象的i属性自增一次并返回。
}
}
if(a==1&&a==2&&a==3){//每次运算都会调用一次a的valueOf方法
console.log(1)
}
带有逻辑非运算符的比较(超级大坑!!!!):
将其他数据类型通过Boolean()转换为布尔类型。
除了0,-0,NaN,undefined,null,空字符串,false,document.all()这八种情况外,其他都会被转换为true。
//注意:只有带有!才会转换为布尔型
console.log([]==0)//true 这里是通过[].valueOf().toString()先转换为空字符串,然后空字符串转换为数字0来比较的
console.log(![]==0)//true
console.log({}==!{})//false
//这里设置两种转换,前者通过{}.valueOf().toString()转换为'[object Object]',然后转换为1。后者直接可以转换为false,然后转换为0
console.log([]==![])//true
//同理,前者转换为空字符串,然后转换为数字0.后者转换为false,然后是数字0;
console.log({}=={})//false 注意:这里是引用数据类型,所以栈中的地址是不同的。
console.log([]==[])
(2)不同数据类型的值的比较,是怎么转换的,有什么规则:
隐式转换!!
(3)unll与undefined:
null表示空对象,undefined表示变量定义了但没赋值。
为什么null == undefined:
ECMAScript规范中是这样定义的…
(4)==和==、以及Object.is的区别
== 比较的是值,会自动进行隐式转换。
===不存在隐式转换,会先进行类型的比较,再比较值。
Object.is()是ES6中新增的语法,与严格比较运算符(===)基本一致。但具有一些特殊案例,这些案例在更准确,更符合实际开发使用:
//最主要的特点:
console.log(NaN==NaN);//false
Object.is(NaN,NaN);//true
console.log(+0==-0);//true
Object.is(+0,-0);//false
--------------------------
console.log(0=='');//true
Object.is(0,'');//false
console.log(null==undefined);//true
Object.is(null,undefined);//false
console.log([1]==true);//true
Object.is([1],true);//false
(5)typeof与instanceof的区别
typeof一般用于检测基本数据类型。返回的值包括string,number,boolean,undefined,function,object.
function Person() {
this.name = "liuxiaokang";
}
var a = [34, 4, 3, 54],
a2 = {},
a3 = null;
var a4 = new Person();
var b = 34,
c = "adsfas",
d = function () {
console.log("我是函数");
},
e = true,
g;
console.log(typeof a); //object
console.log(typeof a2); //object
console.log(typeof a3); //obejct
console.log(typeof a4); //object
console.log(typeof b); //number
console.log(typeof c); //string
console.log(typeof d); //function
console.log(typeof e); //boolean
console.log(typeof g); //undefined
instanceof用于检测引用数据类型,检测某个对象是不是某个构造函数的实例。原理是按照原型链进行查找,查找该对象所在的原型链中是否包含该构造函数的原型对象。
function Man() {}
let man = new Man();
man.__proto__ = null;
console.log(man instanceof Man);//false
console.log([] instanceof Array); //true
console.log([] instanceof Object); //true
自定义instanceof:
function my_instanceof(object, fnName) {
let prototype = object.__proto__;
while (prototype) {
if (fnName.prototype === prototype) {
return true;
} else {
prototype = prototype.__proto__;
}
}
return false;
}
console.log(my_instanceof([], Array)); //true
console.log(my_instanceof([], Object)); //true
console.log(my_instanceof({}, Array)); //false
(6)写一个函数判断变量类型:
方法一:利用typeof和instanceof
// 1.先判断是否为null, (需要使用严格比较运算符, 因为undefined == null)
// 2.然后通过typeof判断出其他的基本数据类型,若为number还需进一步判断是否为NaN,注意:function的检测直接可以在typeof的判断中进行
// 3.对于是obejct的数据在判断的时候需要先判断是否为Obejct,再进一步判断是数组,还是日期,如果都不是,那就是对象
// 总的来说分为3段:null,"非object"和object
function getType(a) {
if (a === null) {
console.log("空值");
return;
}
let state = typeof a;
if (state != "object") {
switch (state) {
case "string":
console.log("字符串");
break;
case "number":
if (isNaN(a)) {
console.log("NaN");
} else {
console.log("数字");
}
break;
case "boolean":
console.log("布尔值");
break;
case "undefined":
console.log("未赋值变量");
break;
case "function":
console.log("函数");
}
} else {
if (a instanceof Object) {
if (a instanceof Array) {
console.log("数组");
} else if (a instanceof Date) {
console.log("时间");
} else {
console.log("对象");
}
}
}
}
getType("sss"); //字符串
getType(21312); //数字
getType(true); //布尔值
getType(null); //空值
let m;
getType(m); //未赋值变量
getType([]); //数组
getType({}); //对象
getType(new Date()); //时间
getType(function () {}); //函数
getType(NaN); //NaN
方法二:利用Obejct.prototype.toString()方法(参照js笔记-25.toString方法.js)
function getType(value) {
let toString = Object.prototype.toString;
let str = toString.call(value);//[object Function]
let type = str.substring(8, str.length - 1);
//上述可写为:
//let type= str.match(/\[object (.*?)\]/)[1]
// 这里需要额外判断NaN,注意:不能使用isNaN,因为存在Number转换
if (Number.isNaN(value)) {
console.log("NaN");
return;
} else {
console.log(type);
}
}
getType("sss"); //String
getType(21312); //Number
getType(true); //Boolean
getType(null); //Null
let m;
getType(m); //Undefined
getType([]); //Array
getType({}); //Object
getType(new Date()); //Date
getType(function () {}); //Function
getType(NaN); //NaN
5.数组:
(1)数组的方法:
添加元素:push unshift
删除元素:pop shift
在任意位置添加或删除元素:splice
数组合并:concat(原不变)
**排序相关:**翻转数组 reverse 排序 sort
迭代器函数相关:
every some forEach filter map reduce
reduce,reduceRight:返回一个将被叠加到累加器的值,reduce 方法停止执行后会返回
这个累加器。
[1,2,3,4,5].reduce((a,b)=>a+b);//15 求数组的和
搜索相关:
indexOf lastIndexOf 返回第一个或最后一个匹配到的元素的索引值
find findIndex 返回第一个满足条件的值或索引
[1, 5, 10, 15].find(function(value, index, arr) {
return value > 9;
}) // 10
includes 查询指定的索引位置后是否存在某元素
输出数组为字符串:
toString()
join()
ES6新增:
for…of遍历数组
entries,keys,values 生成一个迭代器
Array.from() Array.of() 创建数组
fill 填充数组
copywithin 数组内部元素替换
其他:
valueOf,toString
join
slice
(2)数组去重(7种):
https://segmentfault.com/a/1190000016418021?utm_source=tag-newest
// 方法1.indexOf方法,判断新数组中是否存在该元素
let arr = [11, 22, 33, 22, 44];
let newArr = [];
arr.forEach((item) => {
if (newArr.indexOf(item) == -1) {
newArr.push(item);
}
});
console.log(newArr); //[ 11, 22, 33, 44 ]
// 方法2:将数组的每个元素存储在对象的键中,若后面有重复,则删除数组中的该元素
let arr2=[11, 22, 33, 22, 44];
let obj = {};
let newArr2 = [];
for(let i=0;i<arr2.length;i++){
var tmp=arr2[i];
if(obj[tmp]){
arr2.splice(i,1);
i--;
}else{
obj[tmp]=tmp;
}
}
console.log(arr2);//[ 11, 22, 33, 44 ]
// 方法3:双重for循环+splice 一个元素一个元素的比较,删除重复的值
let arr5 = [11, 22, 33, 22, 44];
for (let i = 0; i < arr5.length; i++) {
for (let j = i + 1; j < arr5.length; j++) {
if (arr5[i] == arr5[j]) {
arr5.splice(j, 1);
j--;
}
}
}
console.log(arr5); //[ 11, 22, 33, 44 ]
// 方法4:sort排序+for循环 排序后,将后面一项与前一项进行比较
let arr6 = [11, 22, 33, 22, 44];
arr6.sort();
let newArr6 = [];
newArr6.push(arr6[0]);
for (let i = 1; i < arr6.length; i++) {
// 如果后一项与前一项不同,则向新数组中添加元素
if (arr6[i] != arr6[i - 1]) {
newArr6.push(arr[i]);
}
}
console.log(newArr6); //[ 11, 22, 22, 44 ]
// 方法5:利用filter 只返回第一次出现的值
let arr7 = [11, 22, 33, 22, 44];
let newArr7 = arr7.filter((item, index) => {
// 只返回数组中第一次出现的该值
return arr7.indexOf(item) == index;
});
console.log(newArr7); //[ 11, 22, 22, 44 ]
// 方法6:ES6 Set集合+Array.from()
let newArr3 = Array.from(new Set(arr)); //通过Array.from将具有iterator接口的数据结构转换为数组
console.log(newArr3); //[ 11, 22, 33, 44 ]
// 方法7:ES6 Set集合+ ...展开运算符
let newArr4 = [...new Set(arr)];
console.log(newArr4); //[ 11, 22, 33, 44 ]
(3)如何判断一个变量是否为数组(4种):
let a=[];
//方法1:instanceof
console.log(a instanceof Array);//true
//方法2:Array.isArray
console.log(Array.isArray(a));//true
//方法3:Object.prototype.toString
let state=Object.prototype.toString.call(a).includes("Array");
console.log(state)//true
//方法4:判断该对象的原型对象中的constructor属性指向的构造函数是否为Array
console.log([].__proto__.constructor.toString().includes("Array"))//true
console.log({}.__proto__.constructor.toString().includes("Array"))//false
(4)数组遍历方法的比较:
1.for循环(缺点:写法麻烦)
2.forEach
数组的每个元素都会执行指定的函数,无法通过break和return跳出循环
3.for…in
主要是为遍历对象设计的,遍历出的对象的键值,且是字符串的形式
还会遍历出原型链上的属性
for (const i in [1, 2, 3, 4]) {
console.log(i);// 0 1 2 3
console.log(typeof i);//string
}
let o = {
name: "liu",
age: 21,
};
o.__proto__.sing = function () {
console.log("sing");
};
console.log(o);
for (let key in o) {
console.log(key);//name age sing
}
4.for…of
跟for…in一样简洁;
可以与break,continue,return搭配使用;
可以遍历所有数据结构。一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for…of循环遍历它的成员。for…of的原理是该语法内部会自动调用该数据结构的Symbol.iterator属性,然后根据next获取其中的值。
原生具备iterator接口的数据结构:Array String Set Map arguments对象 (DOM NodeList 对象) Generator 对象;
Object不具有Iterator接口,所以不能通过for…of遍历。
for (const i of [1, 2, 3, 4, 5]) {
if (i > 4) {
return;
}
console.log(i); //1 2 3 4
}
6.String包装对象:
(1)String的方法:
indexOf() lastIndexOf()
charAt()
charCodeAt()
concat()
substring() substr()
split()
trim()
toLowerCase() toUpperCase()
slice()
replace()
match()
search()
ES6新增:
String.prototype.includes()
(2)字符串转数字的方法:
parseInt() parseFloat()
Number()
‘12121’-0 隐式转换
(3)string的startwith和indexof两种方法的区别:
indexOf是ES5中的方法,用于返回字符串中某个字符出现的位置,返回值为数字。
startwith是ES6新增的方法,用于检测某字符是否在原字符串的头部,返回值为布尔值。第二个参数表示开始搜索的位置。
let s = 'Hello world!';
s.startsWith('Hello') // true
s.startsWith('world', 6) // true
7.Object:
怎么获得对象上的属性:
for…in 注意:还会遍历出对象原型对象上的方法,可以通过obj.hasOwnProperty(key)选择属于自身的属性。
Object.keys() 会返回一个包含属性的数组。
Object.getOwnPropertyNames() 返回一个包含属性的数组。
前两种只能遍历出可枚举的属性,第三种还能遍历出不可枚举的属性。