1. 判断对象为空的方法
① 使用JSON.stringify转换为字符串
var temp = JSON.stringify(data);
if (
JSON.stringify(data) === "{}" ||
JSON.stringify(data) === "[]" ||
JSON.stringify(data) === "null"
) {
console.log("空对象");
} else {
console.log("非空对象");
}
*②使用for...in查找key
var judgeEmpty = function () {
for (var key in data) {
if (key) {
return "非空对象";
}
}
return "空对象";
};
console.log(judgeEmpty(data));
③使用ES6自带的,Object.keys(data)返回的是key值的数组
Object.keys(data).length === 0
④使用Object对象的Object.getOwnPropertyNames
Object.getOwnPropertyNames(data).length === 0
2. Object.keys和Object.getOwnPropertyNames的区别
Object.key和Object.getOwnPropertyNames都是获取对象的key值,但是Object.key获取的是可枚举的,后者则是全部列出来。需要注意的是Symbol类型的key,都无法获取
var obj = {};
var id = Symbol("id")
Object.defineProperties(obj, {
a: { enumerable: true, value: 1 },
b: { enumerable: false, value: 2 },
[id]: { enumerable: true, value: 3 }
});
for (var key in obj) {
console.log(key, obj[key]);
}
// ->> a 1
console.log(Object.keys(obj));
// ->> ["a"]
console.log(Object.getOwnPropertyNames(obj));
// ->> ["a", "b"]
3. null和undefined的区别
- 类型
typeof null; // object
typeof undefined // undefined
- 类型转换
console.log(Number(undefined), Number(), Number(null)); // NaN 0 0
console.log(!null, !undefined); // true true
- 类型判断
null == undefined // true
null === undefined // false
- 出现场景
null表示为空对象,没有这个对象;
① 定义一个变量的初始值
var a = null;
② 作为函数中传参,某些参数为空
function print(p1, p2, p3) {
console.log("p1=" + p1, "p2=" + p2, "p3=" + p3);
}
print(1, null, 3);
③ 作为对象原型链的终点
console.log(Object.getPrototypeOf(Object.prototype)); // null
undefined表示此处应该有一个值,但是未定义,表示原始值;
①定义了变量未赋值
var a;
console.log(a);
②对象中不存在的属性
console.log(console.a);
③函数中参数未赋值
function print(a) {
console.log(a)
}
print();
4. typeof和instanceof区别
typeof可判别的类型有:
number,string, boolean, bigint, symbol, object, function
但是对于object, array, null都会被判别为object
console.log(typeof {}, typeof [], typeof null); // object object object
instanceof是判别对象实例是谁的,比较是否是同一个原型
function Person() {
var name = "lin";
}
var a = new Person();
console.log(a instanceof Person); // true
console.log(Object.getPrototypeOf(a) === Person.prototype); // true
对于typeof无法区别的类型
console.log([] instanceof Array); // true
但是需要注意的是原始数据类型的类型判断,只有转成基本包装类型对象才能正确比较
console.log(1 instanceof Number); //false
var num = new Number(1);
console.log(num instanceof Number); // true
5. 判断一个对象为null,undefined, NaN,JSON对象,数组方法
- 判断为null
data === null
Object.prototype.toString.call(data) == "[object Null]"
- 判断为undefined
这个比较简单
typeof data === 'undefined'
- 判断为NaN
isNaN(data)
- JSON对象(有问题,如果是new Object()的对象)
typeof(data) == 'object' && Object.prototype.toString.call(data) == "[object Object]"
&& !data.length
- 判断为数组
data instanceof Array // 如果是数组data instanceof Object也是true
Object.prototype.toString.call(data) == "[object Array]"
Array.isArray(data)
6. == 和 ===区别,效率上哪个更高
== 判断规则:
首先先判断类型,相同则直接比较大小,如果是number类型的,其中有NaN,结果为false
NaN == NaN // false
如果不相同,则按照如下规则转换
(1)如果是为null == undefined的比较,则返回 true
(2)如果是string == number两种类型数据的比较,则将string的转为number再比较
(3)如果是boolean == any,先Number(boolean),再比较规则从头再来比较
(4)如果是object == string | number | symbol,则会将object转为基本类型,转换方式
先调用valueOf(),如果结果为基本类型则返回,否则调用toString方法,结果为基本类型则返回,否则比较结果为false
(5)除此之外的情况,统统返回false
转换方法:
- data.valuef()
- data.toString()
举个例子:
var data = {
valueOf() {
return 1;
},
toString() {
return "a";
}
console.log(data == 1, data == "a"); // true false
也可以自定义转换方法
var data = {
valueOf() {
return 1;
},
toString() {
return "a";
},
[Symbol.toPrimitive]() {
return "b";
},
};
console.log(data == 1, data == "a", data == "b"); // false false true
https://user-gold-cdn.xitu.io/2018/12/19/167c4a2627fe55f1?w=1005&h=426&f=png&s=38534&ynotemdtimestamp=1589420716562
参考链接
比较方式流程图:
=== 判断规则:
(1)如果类型不一样,则返回false
(2)类型相同,如果同样是null或者同样是undefined就返回true
(3)如果有NaN,则返回false,即NaN === NaN 结果为false
(4)如果数值+0和-0 比较结果是true,数值相同为tru
(5)同一个Symbol,结果是true
(6)x和y同为true或false结果为true
(7)字符串完全相同为true
其他情况为false
送命题:
①[] == ![]的结果: true
分析:
![] 的结果为false,为boolen类型,--》0
[]为对象则转为基本类型
[].valueOf()结果为[]
[].toString()结果为""
"" == 0, Number("")结果为0
所以结果为true
②{} == !{}的结果: false
分析:
{}.valueOf() // {}
{}.toString() // [object Object]
③{} == {}的结果:false
因为{}是对象,两个{}的地址不同,自然就不同,像下面这种就是一样的
var a = {};
b = a;
console.log(a == b); //true
④[] == []的结果:false
与③同理
==和===效率比较
在Array, Object地址比较则没有区别,两个都需要操作类型,判断类型,但是==必要时候还需要类型转换,这时候===效率会高一点
7. bind、call、apply的区别
这三个都是用来改变上下文的执行环境,即改变this的指向。
bind和其他两个的区别是bind是返回这个函数,而另外两个则是会直接执行。
call和apply第一个参数都是指向,后面的参数,call是用列表的形式传递,apply是用数组的形式传递
使用方式:
class C1 {
constructor(name) {
this.name = name;
}
showName = function () {
console.log(this.name);
};
}
var f1 = new C1("b");
f1.showName(18, "man"); // b 18 man
f1.showName.call(obj1, 19, "man"); // a 19 man
f1.showName.apply(obj1, [20, "man"]); // a 20 man
var func = f1.showName.bind(obj1);
func(21, "man"); // a 21 man
应用:
①将伪数组转为数组
比如操作dom时候获取div对象数组,类型为HtmlCollection,我们又想要它可以使用数组的操作方法,则
var div = document.getElementsByTagName("div");
var arr1 = Array.prototype.slice.call(div);
//或者
var arr2 = [].slice.call(div);
或者使用ES6的方法:
Array.from(div) //Array的内置方法
[...div] // 扩展运算符
数组调用slice()返回的就是数组
②将函数的arguments转为数组
这时候使用apply和call都是可以的
function func1() {
console.log(Array.prototype.slice.call(arguments));
console.log(Array.prototype.slice.apply(arguments));
}
func1(1, 2, 3)
③将对象转为数组
var obj = {
0: "a",
1: 18,
length: 2,
};
console.log(Array.prototype.slice.call(obj));
注意对象中一定要有索引,length
④获取对象的类型
这个是最好用的,获取类型的方式,上面也有提到
var obj = [];
Object.prototype.toString.call(obj) // [object Array]
⑤操作数组
var arr1 = [1, 2];
var arr2 = [4, 5];
var res = Array.prototype.concat.apply(arr1, arr2);
console.log(res, arr1, arr2);
// [1, 2, 3, 4] [1, 2] [4, 5]
Array.prototype.push.apply(arr1, arr2);
console.log(arr1, arr2);
// [1, 2, 3, 4] [4, 5]
如果使用concat,则结果是返回的值,在原来的数组不发生改变;
如果使用push,则没有返回值,结果添加到第一个参数中;
⑥实现继承
function Animal(name) {
this.name = name;
this.showName = function () {
console.log(this.name);
};
}
function Dog(name) {
Animal.Dog(this, name);
}
将this对象代指Animai,并且传参name,让Dog可以使用Animai的属性和方法,同样也可以实现多继承
8. 浅拷贝和深拷贝
拷贝是在开发过程中经常遇到的。我们知道数据类型分为基本数据类型和引用数据类型,引用数据类型是引用对象的地址,存在栈内存中, 实际的数据则是在堆内存中。
赋值和浅拷贝的区别
和原来数据是否指向同一个 | 改变第一层数据为基本数据类型 | 改变子对象(原数据不止一层) | |
---|---|---|---|
赋值 | yes | 一起变 | 一起变 |
浅拷贝 | no | 不影响原来的 | 一起变 |
深拷贝 | no | 不影响原来的 | 不影响原来的 |
赋值操作:
var a = [0, 1];
var b = a;
b[0] = 2;
console.log(a, b); // a和b都是[2, 1]
a和b都是指向同一个对象引用
浅拷贝:
浅拷贝会创建一个新的对象,指向某个对象的指针,并不会复制对象本身。如果属性是基本数据类型则拷贝值,如果是引用类型,则还是拷贝引用地址,所以实际上只拷贝了一层。
① 使用Object.assign()
let obj = {
id: 0,
info: { address: { province: "Fujian" }, name: "lin" },
};
let obj2 = Object.assign({}, obj);
obj2.id = 1;
console.log(obj, obj2); //不改变原对象
obj2.info.name = "chen";
console.log(obj, obj2); // 一起改变
②使用数组的操作方法
let arr = [1, { index: [2, 3] }];
let res = arr.concat();
res[0] = 0; // 不改变原对象
res[1].index = [0]; // 一起改变
console.log(arr, res);
同样的slice方法也是这样的
③使用扩展运算符
[...obj]
深拷贝:
会创造有一个一样的对象,操作对象,对原来的对象没有影响,就是完全复制。
① 使用JSON.stringfy的方法
let obj = {
id: 0,
info: { name: "lin" },
};
let temp = JSON.parse(JSON.stringify(obj));
temp.info.name = "chen";
console.log(obj, temp); //不改变原对象
但是使用这个会有局限性:
- 会忽略undefined、Symbol、无法序列化function
let obj = {
id: 0,
info: { name: "lin" },
age: undefined,
index: Symbol("index"),
};
let temp = JSON.parse(JSON.stringify(obj));
console.log(temp);
//结果: { id: 0, info: { name: "lin"} }
- 如果有循环引用则会报错
let obj = {
info: { name: "lin" },
};
obj.other = obj.info;
obj.info.other = obj.other;
let temp = JSON.parse(JSON.stringify(obj));
console.log(temp);
② 使用MessageChannel
这个可以拷贝含有undefined类型的,但是依然无法拷贝函数
function messageClone(data) {
return new Promise((resolve) => {
let { port1, port2 } = new MessageChannel();
port2.onmessage = (e) => resolve(e.data);
port1.postMessage(data);
});
}
messageClone(obj).then((res) => {
console.log(res);
});
③使用lodash函数库
var lodash = require("lodash")
var temp = lodash.cloneDeep(obj)
9. 原型
当我们打印一个对象:
var a = { name: "a" }
console.log(a)
打印结果如上,每个对象都有一个__proto__
属性,通过这个可以找到原型,构造函数则可以通过prototype找到原型。
[图片上传失败...(image-3300a8-1589858753415)]
需要记住的是:
- 对象都有
__proto__
属性,通过这个属性最终都可以找到对象原型:Object.prototype - 函数可以通过
__proto__
最终找到函数原型:Function.prototype - 函数也是对象,所以函数原型通过
__proto__
可以找到Object.prototype -
Object.prototype的
__proto__
指向null -
__proto__
将对象和原型连接起来就是原型链
10 .ES6新特性
ES6(ECMAScript 2015),ECMAScript是ECMA制定的标准化脚本语言。
新增特性:
- 类(Class)
便于理解面向对象的编程,类似于java,也有继承
- 模块化
采用模块化,export和import导入导出,每个模块都有单独的作用域,创造了明明空间,可以防止函数的命名冲突。
- 箭头函数
箭头函数中没有this,如果使用this则是指向父级的this,获取的arguments也是父级的。
var foo = 2;
let a = {
foo: 1,
bar: () => console.log(this.foo),
};
如上,这个例子中箭头函数的this.foo其实指向的是外面的foo,值为2;
如果用普通函数的话,他的结果就是1
适用场景:
① 一些对象遍历过程中的map,reduce,filter
②用于简洁操作,比如直接返回表达式的结果
③在任何需要绑定 this 至当前上下文,而不是函数本身时
缺点:
① 不适合作为对象中的方法
② 带有动态上下文的回调函数,比如addEventListenr,不利于解绑
③ 影响代码可读性,不利于测试,不容易追溯代码
- 可设置函数参数默认值
function test(a = 0) {
console.log(a)
}
- 可以使用模板字符串
var name ="a"
console.log(`mmy name is ${name}`)
- 解构赋值
var index = [0, 1, 2]
var [zero, one, two] = index
console.log(zero) // 0
// 获取对象的某些属性
var obj = { age: 18, name: "a"}
var {name} = obj
- 扩展操作
let clone = { ...obj };
浅拷贝对象
- 对象简写
let name='a'
let student = {
name
};
console.log(student);//{name: "a"}
- promise
异步操作
- let和const
let和const都是块级作用域
补充(ES7新特性)
- includes
检测数组中是否有某个值
- 可以使用指数操作:
2**3 结果为8
补充(ES8新特性)
重点是async/await
11. 数组常用操作方法
-
数组的转换方法
数组常见的转换方法有toString(),toLocalString(),valueOf()
const value = [1, 2, 3]; console.log(value.toString()); // 1, 2, 3 console.log(value.toLocaleString()); // 1, 2, 3 console.log(value.valueOf()); // // [1, 2, 3]
toString返回的是所有元素用逗号拼接的结果;
toLocalString表示在特定环境下要表示的字符串;
valueOf返回的是数组本身;
请看这个例子:
const a = new Object([1, 2, 3]); console.log(a.toString()); // 1,2,3 console.log(Array.prototype.toString.call(a)); // 1,2,3 console.log(Object.prototype.toString.call(a)); // [Object Object]
我们发现最后一个的结果并没有打印出1,2,3,那是因为Array继承了Object,重写了toString方法
-
将对象转换为数组
如果一个对象有索引值,且含有length属性,则可以转为数组,方法有:
const person = { 0: "age", 1: "name", length: 2, }; console.log([].slice.call(person)); console.log(Array.from(person)); console.log(Array.from(person, (x) => "head-" + x)); console.log(person);
对应的结果如下:
[ 'age', 'name' ] [ 'age', 'name' ] [ 'head-age', 'head-name' ] { '0': 'age', '1': 'name', length: 2 }
Array.from还接受2个参数,第二个可以用来对数组的处理,这些转换都不回影响原来的json对象
Array.isArray
判断是否是数组,其他方式:
arr instanceof Array
arr.constructor === Array
Object.prototyp.toString.call(arr) === '[object Array]'
-
arr.concat(arr2,arr3,...)
返回合并后的数组,原数组不改变
-
arr.join(符号)
根据某个符号拼接数组成字符串
-
map
遍历数组,根据一定的操作对灭一个元素操作再返回
-
filter
筛选符合条件的所有元素,并返回
-
indexOf
查找某个元素的索引,找不到为-1
-
find
返回第一个符合条件值
-
findIndex
找到符合条件的第一个索引值
-
includes
判断是否包含某个元素
-
reduce
对数组做一定的运算,比如求和
reduce用法实例
const nums = [1, 3, 5, 7]; const res = nums.reduce((pre, current, index, arr) => { console.log(pre, current, index, arr); return pre + current; }, 0); console.log(res);
模拟函数的写法:
function reduceAction(arr, func, initValue) { let originArr = arr.slice(); // 保留原数组 let newArr = (typeof initValue !== "undefined" ? [initValue] : []).concat( originArr ); // 判断是否有初始值 let index = 0; while (newArr.length > 1) { newArr.splice(0, 2, func(newArr[0], newArr[1], index++, originArr)); // 删除前两个元素,并且调用func函数,为了保留索引,设置自增的index } return newArr[0]; // 最后剩下的一个元素,则是reduce的结果 }
-
some
只要有一个元素符合条件,结果就是true
-
every
每一个元素都符合条件,结果才是true
-
pop(原数组会影响)
删除数组最后一个元素,并返回
-
push(原数组会影响)
为数组添加元素
-
shift(原数组会影响)
删除数组第一个元素,并返回这个元素
-
unshift(原数组会影响)
在数组开头添加元素,并返回长度
-
reverse(原数组会影响)
逆序
-
slice(begin, end)
截取数组,如果begin和end是负值,则加上数组的长度,如果begin >= end,则返回空数组,使用slice对数组的切割是不会影响原数组的
const color = ["red", "yellow", "blue", "green", "black"]; console.log(color.slice(1, -1)); console.log(color.slice(3, 4)); console.log(color.slice(3, 1)); console.log(color.slice(-2, -1)); console.log(color);
对应结果:
[ 'yellow', 'blue', 'green' ] [ 'green' ] [] [ 'green' ] [ 'red', 'yellow', 'blue', 'green', 'black' ]
splice(原数组会影响)
第一个参数是开始位置,第二个参数是删除的数量,第三个参数是需要在开始位置插入的元素,结果是返回删除的元素,如果只有第一个参数,则从当前位置删除到末尾位置。
var arr = [1, 2, 3, 4];
console.log(arr.splice(2)); //[3, 4]
console.log(arr); // [1, 2]
如果第一个参数是负数,则会默认加上数组的长度,如果截取的长度超过可取得长度,则截取剩余所有的元素,例如:
const color = ["red", "yellow", "blue", "green", "black"];
console.log(color.splice(-1, 2)); // [ 'black' ]
console.log(color); // [ 'red', 'yellow', 'blue', 'green' ]
如下例子是从索引1开始,删除两个元素,并在索引1的位置插入orange
const color = ["red", "yellow", "blue", "green", "black"];
console.log(color.splice(1, 2, "orange")); // [ 'yellow', 'blue' ]
console.log(color); // [ 'red', 'orange', 'green', 'black' ]
-
sort(原数组会影响)
排序,可以传入参数定义具体的排序函数
-
includes
用于检查数组中是或否有某个元素
12. 字符串常用操作方法
- 对字符串的遍历let ... of
printStr() {
let str = 'my name is Linyq'
for (let a of str) {
console.log(a)
}
}
- 查找字符串indexOf (数组操作中也有)
let str = 'yo world, world!'
console.log(str.indexOf('wo')) // 3
console.log(str.indexOf('w', 4)) // 10
console.log(str.indexOf('b')) // -1
-
includes():(数组操作中也有)
返回布尔值,表示是否找到了参数字符串。
-
charAt
根据索引获取指定字符
-
concat
连接两个字符串
-
slice(start, end)
(1) start和end如果是负数,则加上字符串长度值
(2) 没有指定end默认到头
var str = "abcde" consol.log(str.slice(-2)) // de
-
substr(start, length)
(1) start和length<=0,则默认为0
(2)没有指定lenght默认到头
-
substring(start, end )
(1) start 比 end 大,那么该方法在提取子串之前会先交换这两个参数
(2) start和length<=0,则默认为0
-
toLowerCase
转成小写
-
toUpperCase
转成大写
-
startsWith():
返回布尔值,表示是否在原字符串的头部。
-
endsWith():
返回布尔值,表示是否在原字符串的尾部
repeat
'x'.repeat(3) // xxx
'ab'.repeat(2) // abab
'x'.repeat(0) // ''
replace替换字符
字符串补充,padStart和padEnd,第一个参数是补充到的位数
// padStart是在开头补,padEnd是在结尾补
'xx'.padStart(5, 'ab') // abaxx
'xx'.padStart(5, '0123456') // 012xx
'xx'.padEnd(2, '012') // xx
- 去掉空格
// trim去掉两端的空格
const s = ' abc '; s.trim() // "abc"
// 开头去掉空格
s.trimStart() // "abc "
// 结尾去掉空格
s.trimEnd() // " abc"