目录
1.JS数据类型及它们的区别
基本数据类型:
引用数据类型:
区别:
2.基本数据类型中的null和undefined的区别和应用
1.区别
2.什么时候会有null
3.什么时候会出现undefined
3.数据类型检测的方式
1.typeof
2.instanceof
3.constructor
4. Object.prototype.toString.call( )
4.instanceof操作符的实现原理及实现
手写instanceof的实现原理
5.typeof NaN的结果
6.判断数组的方法
1.通过Object.prototype.toString.call()做判断
2.通过原型链做判断
3.通过ES6的Array.isArray()做判断
4.通过instanceof做判断
5.通过Array.prototyps.isPrototypeOf做判断
7.+操作符什么时候用于字符串的拼接
8.Object.is和===,==的区别
1.==双等号:
2.===三等号:
3.Object.is( ):
9.如何处理隐式转换
1.ToPrimitive方法
如果值为基本类型,则直接返回值本身;
如果值为对象,其看起来大概是这样:
1.当type为number是规则如下:
2.当type为string是规则如下:
2.基本数据类型的隐式转换
1.+操作符
2.- , * , / 操作运算符
3.==操作符
4.<和>比较符
4.其他情况下,转换为数字再比较
3.针对对象的隐式转换
10.深拷贝和浅拷贝的区别
1.赋值:
2.浅拷贝:
1.手写浅拷贝
2.Object.assign(target, obj, ···)
3.Array.slice(Array.prototype.slice)和Array.concat(Array.prototype.concat)
3.深拷贝:
1.JSON.parse(JSON.stringify(obj))
2.扩展运算符(第一层)
3.函数库lodash的_.cloneDeep方法deep
4. 手写递归方法
5.循环引用
总共有七种
string/number/boolean/null/undefined / symbol(ES6:创建之后独一无二,并且不可变的数据类型,为了解决全局变量冲突问题)
Object
1.声明变量时的存储分配;
基本数据类型存储在栈中var a=10,
引用数据类型存储在堆中var a=[1,2,3,4],如果再去改变它数组在增加,大小也在变大a=[1,2,3,4,......],大小不固定,会随着长度在增大
2.不同的内存分配也带来了不同的访问机制;
不可以直接访问堆内存空间的位置以及直接操作堆内存空间,只能操作对象在栈内存中的引用地址;
基本数据类型时可以直接访问到,
引用数据类型访问引用地址,根据引用地址找到堆中实体。
3.复制变量时的不同;
基本数据类型:var a=1,var b=a ,将原始值的副本赋值新的变量;传值
引用数据类型:var obj={name:'zz'} ,var obj1=obj,将引用地址赋值给新的变量;传址
var a = [1, 2, 3, 4]
var b = a
var c = a[0]
console.log(b)//(4) [1, 2, 3, 4]
console.log(c)//1
// 改变数值
b[3] = 6
c = 7
console.log(a[3])//6
console.log(a[0])//1
都是代表没有值
console.log(Number(null))//0
console.log(Number(undefined))//NaN
console.log(Object.getPrototypeOf(Object.prototype))//null,为null说明到达原型链的终点了
var a
console.log(a)//1.undefined
// 这个a存在局部作用域,与上面的a不一样
function fun1 (a) {
console.log(a)//2.undefined
}
fun1()
var obj = {}
console.log(obj.name)//3.undefined
var f = fun1()
console.log(f)//4.undefined
检测数据类型的运算符。
console.log("数值", typeof 10)//number
console.log("布尔", typeof true)//boolean
console.log("字符串", typeof '你好')//string
console.log("数组", typeof [])//object,因为它归为引用类型
console.log("函数", typeof function () { })//function
console.log("对象", typeof [])//object
console.log("undefined", typeof undefined)//undefined
console.log("null", typeof null)//object,
//浏览器的bug,因为所有的值在计算机中都是以二进制的编码来进行存储的,把前三位都为0的当作对象;null可以看作时空对象的指针,基本数据类型
检测某一个实例是否属于这个类。可以正确判断对象的类型,不可以判断基本上就类型;内部运行机制,判断在它的原型链上能否找到这个类型的原型。
null和undefined不可
console.log("数值", 10 instanceof Number)//false
console.log("布尔", true instanceof Boolean)//false
console.log("字符串", '你好' instanceof String)//false
console.log("数组", [] instanceof Array)//true
console.log("函数", function () { } instanceof Function)//true
console.log("对象", {} instanceof Object)//true
检测实例和类的关系(根据实例和类的关系,从而检测数据类型)。constructor作为object的属性,这个属性就是引用原来构造该对象的函数。
null和undefined不可
console.log("数值", (10).constructor === Number)//true
console.log("布尔", (true).constructor === Boolean)//true
console.log("字符串", ('你好').constructor === String)//true
console.log("数组", ([]).constructor === Array)//true
console.log("函数", (function () { }).constructor === Function)//true
console.log("对象", ({}).constructor === Object)//true
检测数据类型。使用对象原型的toString的方法。
var a = Object.prototype.toString
console.log("数值", a.call(10))//[object Number]
console.log("布尔", a.call(true))//[object Boolean]
console.log("字符串", a.call('你好'))//[object String]
console.log("数组", a.call([]))//[object Array]
console.log("函数", a.call(function () { }))//[object Function]
console.log("对象", a.call({}))//[object Object]
instanceof操作符用于判断构造函数的prototype属性是否出现在对象的原型链中的任何位置。
console.log({} instanceof Object)//true
// 构造函数也可以称为一类
// 1.定义构造函数
function Person (name) {
this.name = name
}
// 2.实例化对象
let obj = new Person('张三')
// 1.obj的原型,看实例对象
console.log(Object.getPrototypeOf(obj))
//constructor: ƒ Person(name)说明两者有关系
// 2.构造函数的原型,看构造函数
console.log(Person.prototype)//Person自身有原型属性
// constructor: ƒ Person(name)
// 3.看两者是否有关系,是否相等
console.log(Object.getPrototypeOf(obj) === Person.prototype)//true
// 说明obj的原型是等于构造函数的原型属性,
// 构造函数的prototype属性出现在对象的原型链中
// Person是构造函数,也可以称为是一类,obj属于Person这一类
console.log(obj instanceof Person)//true
console.log(obj instanceof Object)//true
console.log(Person.prototype)//constructor: ƒ Person(name)
console.log(Object.prototype)//constructor: f Object(),object和Person原型属性两个不相等
console.log(Object.getPrototypeOf(Object.getPrototypeOf(obj)))//constructor: f Object(),两个相等
console.log(Object.getPrototypeOf(Object.getPrototypeOf(obj))===Object.prototype)//true
obj这一类也出现在了Object的原型链上面,只不过它的原型还要在往上在找一次
// 手写instanceof的实现原理
// 1.定义一个函数,里面有两个行参left,right
function instance (left, right) {
// 2.获取对象(left)的原型
let proto = Object.getPrototypeOf(left)
// 3.(拿到right的属性)获取构造函数的prototype属性
let prototype = right.prototype
// 4.
while (true) {
// 4.判断原型能不能获取到,拿不到则返回false
if (!proto) {
return false
}
// 5.判断这个对象的原型跟构造函数的原型属性是否相等,如果相等则返回true
if (proto === prototype) {
return true
}
// 说明上面两个if都不成立,原型对象不等于构造函数的prototype属性。
// 又获取proto的原型,再次赋值
// 如果没有找到,就继续在原型链上找
proto = Object.getPrototypeOf(proto)
// 相当于console.log(Object.getPrototypeOf(Object.getPrototypeOf(obj)))
}
}
NaN(not a number)不是一个数字,表示是否属于number类型的一种状态:是或否,不是确切的值。
JS中number数据类型除了浮点型和整数型,还有一个特殊值NaN
console.log(typeof NaN)//number
var a = "abc"
// 当一个表达式输出NaN,表达式中存在不可转化的变量,并且返回了无效的结果,不是返回确切的值
console.log(Number(a))//NaN
// 它与自身不相等,因为NaN不是一个确切的值,代表一个范围
console.log(NaN === NaN)//false
console.log(NaN !== NaN)//true
console.log(Object.prototype.toString.call([]))//[object Array]打印的是结果
// 如果想做判断的话,需要拿到字符串的关键信息Array
// 用到字符串截取slice(包头不包尾)
console.log(Object.prototype.toString.call([]).slice(8,-1)==='Array')//true
如果对象的隐式原型等于构造函数的显式原型的话,那么说明这个对象就是这个构造函数实例化出来的。
// 对象隐式原型和构造函数的显式原型
console.log([].__proto__ === Array.prototype)//true
console.log(Array.isArray([]))//true
跟原型链挂钩的。
console.log([] instanceof Array)//true
判断Array是否在传入内容的原型链上。
console.log(Array.prototype.isPrototypeOf([]))//true
如果+操作符其中一个操作数是字符串()(或者是通过ToPrimitive操作之后,最终得到的字符串),则执行字符串的拼接,否则执行数字加法。
var a = { name: 'zz' }//-->[object Object]
// a.valueOf()
// a.toString()
var b = { age: 11 }//-->[object Object]
// b.valueOf()
// b.toString()
var c = 1
console.log(a + c)//[object Object]1
console.log(a + b)//[object Object][object Object]
console.log(1 + 1 + '23')//2+'23'->223
console.log(1 + '23' + 4 + 1)//'123'+4+1->'1234'+1->12341
Object.is()和比较运算符“===”(严格相等运算符)和“==”(相等运算符)的区别
如果两边的类型不一致,进行强制类型转换,然后再去进行比较。
console.log(1 == true)//-->1==1-->true
console.log(1 == '1')//-->1==1-->true
如果两边类型不一致,不会进行强制类型转换,直接返回false-->等型等值。
console.log(1 === true)//false
console.log(1 === '1')//false
console.log(NaN === NaN)//false
console.log(+0 === -0)//true,隐式转换
判断两个值是否严格相等。处理一些特殊的情况,-0和+0不再相等,两个NaN是相等的。
console.log(Object.is(+0, -0))//false
console.log(Object.is(NaN, NaN))//true
ToPrimitive方法(转换为基本数据类型):这是JS中每个值隐含的自带的方法,用来将值(无论是基本类型值还是对象类型)转换为基本类型值。
(1)调用obj的valueOf方法(返回对象的原始值),如果为原始值,则返回,否则下一步
(2)再调用obj的toString方法(转换为字符串类型),如果为原始值,则返回,否则下一步
(3)抛出TypeError异常
(1)调用obj的toString方法,如果为原始值,则返回,否则下一步
(2)再调用obj的valueOf方法,如果为原始值,则返回,否则下一步
(3)抛出TypeError异常
对象的data的type默认为string,其他的一般为number。
var objToNumber = function (value) {
return Number(value.valueOf().toString())
}
console.log(objToNumber([]))//数组最后转为数字类型之后的结果-->0
console.log(objToNumber({}))//对象转为数字类型之后的结果-->通过toString转变为字符串[object Object]-->在通过Number去转换时,转换不了-->NaN
把两边的值通过ToPrimitive方法转为基本数据类型,再进行操作。
+的两边有至少一个string类型变量时,两边的变量都会被隐式转换为字符串;其他情况下两边的变量都会被转换为数字。
console.log(5 + '12')//512
console.log(1 + false)//1
console.log('1' + false)//1false
console.log(true + false)//1
会转换为数字
console.log(15 - '12')//3
console.log(1 * false)//0
console.log(1 / 'aa')//NaN
会转为数字类型进行比较。
console.log(3 == true)//3-->3,true-->1,false
console.log('0' == false)//'0'-->0,false-->0,true
console.log('0' == '0')//true
按照字母的排列顺序进行比较
console.log('c' > 'b')//true
console.log('de' < 'fg')//true
console.log('12' < 13)//true
console.log(false < -1)//false
var a = {}
console.log(a > 2)//false
console.log(a.valueOf())//{}
console.log(a.toString())//[object Object]
console.log(Number(a.toString()))//NaN
// 1.a.valueOf()-->对象
// 2.a.toString()-->字符串
// 3.Number()-->比较-->NaN
// 4.NaN跟2进行比较-->false
主要在于复制出来的新对象和原来的对象是否会互相影响,改一个,另外一个也会发生改变。
深拷贝和浅拷贝只针对引用数据类型(Object、Array、Function、Date、RegExp等)。
指向同一个存储空间,无论哪个对象发生改变,都是改变同一存储空间的内容。把一个对象赋值给一个新的变量时,赋值是该对象的在栈中的地址,而不是堆中的数据。
var obj = { name: 'zz', age: 10, children: { name: 'ls', age: 18 } }
var obj1 = obj
obj1.age = 20
obj1.children.name = '第二层的名字'
console.log(obj)//{name: 'zz', age: 20}
console.log(obj1)//{name: 'zz', age: 20}
一层二层都改变
仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅拷贝出来的对象也会相应的改变。对对象的各个属性进行依次复制,只拷贝对象的第一层属性。
新对象跟旧对象共享内存,修改其中一个,另一个也会受到影响
// 手写浅拷贝
function shallowCopy (obj) {
//根据object的类型判断是新建一个数组还是对象
let newObj = Array.isArray(obj) ? [] : {}
// 如果不是对象直接返回,只拷贝对象
if (typeof obj !== "object") return
//遍历object,并判断是object的属性,然后拷贝
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key]
}
}
return newObj
}
var obj = { age: 10, children: { sex: 'nv' } }
var obj1 = shallowCopy(obj)
obj1.age = 20
obj1.children.sex = '第二层的性别'
console.log(obj)//{age: 10, children: {…}}
console.log(obj1)//{age: 20, children: {…}}
target:目标对象,obj是源对象。源对象自身的任意多个的可枚举属性拷贝给目标对象,然后返回目标对象。
对象合并:将源对象里面的属性添加到目标对象中去。
若两者的属性名(有同名属性)有冲突,后面的将会覆盖前面的。
只有一个参数,当参数为对象时,直接返回该对象;当参数不是对象时,先将参数转为对象,然后返回。
1.修改obj1.age,改变对象中的基本类型值
花括号{}:目标对象,obj、obj1是源对象。
原对象未改变
(
当object只有一层的时候,是深拷贝 )
// 2.Onject.assigin()方法
var obj = { name: 'zz', age: 10 }
var obj1 = Object.assign({}, obj)
obj1.age = 20
console.log(obj)//{name: 'zz', age: 10}
console.log(obj1)//{name: 'zz', age: 20}
2.修改obj1.age,改变对象中的引用类型值
原对象也被改变
var obj = { name: 'zz', age: { zz: 10 } }
var obj1 = Object.assign({}, obj)
obj1.age.zz = 20
console.log(obj)//{name: 'zz', age: 20}
console.log(obj1)//{name: 'zz', age: 20}
Array.slice和Array.contact方法都不会修改原数组,而是会返回一个对原数组进行浅拷贝的新数组。
1.Array.slice
var obj = [1, 3, [4, 5]]
var obj1 = obj.slice()
//改变基本类型值,不会改变原数组
obj1[1] = 7
//改变数组中的引用类型值,原数组也会跟着改变
obj1[2][0] = 7
console.log(obj)//[1, 3, [7, 5]]
console.log(obj1)//[1, 7, [7, 5]]
console.log(obj1 === obj)//false
2.Array.concat
var obj = [1, 3, { sex: 'nv' }]
var obj1 = obj.concat()
obj1[2].sex = 'nan'
console.log(obj)//[1, 3, {sex: 'nan'}]
console.log(obj1)//[1, 3, {sex: 'nan'}]
console.log(obj1 === obj)//false
var obj = [1, 3, [4, 5]]
var obj1 = obj.concat()
//改变基本类型值,不会改变原数组
obj1[1] = 7
//改变数组中的引用类型值,原数组也会跟着改变
obj1[2][0] = 7
console.log(obj)//[1, 3, [7, 5]]
console.log(obj1)//[1, 7, [7, 5]]
console.log(obj1 === obj)//false
在内存中开辟一块新的地址用于存放复制的对象。递归拷贝对象的所有属性。
新对象跟旧对象不会共享内存,修改其中一个,另一个不会受影响
用JSON.stringify:将js的值(对象或者数组)转为一个JSON字符串(序列化),再用JSON.parse()(反序列化)把字符串解析成对象Object类型。
// JSON.stringify:将js的值(对象或者数组)转为一个JSON字符串
// JSON.parse:用来解析JSON字符串,转换为Object类型
var obj = { a: 0, b: { c: 2 } }
var obj1 = JSON.parse(JSON.stringify(obj))
obj1.a = 7
obj1.b.c = 7
console.log(obj)//{a: 0, b: {c: 2}}
console.log(obj1)//{a: 7, b: {c: 7}}
缺点: 这种方法虽然可以实现数组或对象深拷贝,但不能处理函数。(JSON.stringify():将一个javascript值转换我一个JSON字符串,不能接受函数。)
当object只有一层的时候,是深拷贝,第二层的拷贝还是浅拷贝
var obj = {
name: 'zz',
age: {
zz: 10
}
}
var obj1 = { ...obj }
obj1.name = 'nm'
obj1.age.zz = 7
console.log(obj)//{name: 'zz', age: {age: {zz: 7}}}
console.log(obj1)//{name: 'nm', age: {age: {zz: 7}}}
https://www.lodashjs.com/docs/lodash.cloneDeep#_clonedeepvalue
这个方法类似_.clone,除了它会递归拷贝
value
。(注:也叫深拷贝)。
var _ = require('lodash');
var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);// => false
遍历对象、数组直到都是基本数据类型。再去复制,就是深度拷贝。
法一:
function deepCopy (obj) {
var newObject = Array.isArray(obj) ? [] : {}
if (obj && typeof (obj) === 'object') {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (obj[key] && typeof (obj[key]) === 'object') {
newObject[key] = deepCopy(obj[key])
} else {
newObject[key] = obj[key]
}
}
}
}
return newObject
}
法二:
function deepCopy (object) {
// 处理数组
let newObject = Array.isArray(object) ? [] : {}
// 判断是否是引用类型。不是,直接返回
if (!object || typeof object !== "object") return
for (let key in object) {
if (object.hasOwnProperty(key)) {
newObject[key] = typeof object[key] === "object" ? deepCopy(object[key]) : object[key]
}
}
return newObject
}
测试数据:
var obj1 = {
a: 1,
b: {
c: 2
}
}
var obj2 = deepCopy(obj1)
obj2.a = '7'
obj2.b.c = '7'
console.log('obj1', obj1)
console.log('obj2', obj2)
父级引用:当对象的某个属性,正是这个对象本身(两个互相引用的对象),可能会出现子元素->父对象->子元素...死循环,导致栈溢出。
解决办法:需要判断一个对象的字段是否引用了这个对象或这个对象的任意父级(在遍历的时候判断是否互相引用)
var obj1 = {
a: 1,
b: {
c: 2
}
}
var obj2 = deepCopy(obj1)
obj2.z = obj2
console.log('obj1', obj1)
console.log('obj2', obj2)
function deepCopy (obj, parent = null) {
// 创建一个新对象
var newObject = Array.isArray(obj) ? [] : {}
let _parent = parent
// 该字段有父级则需要追溯该字段的父级
while (_parent) {
// 如果该字段引用了它的父级,则为循环引用
if (_parent.originalParent === obj) {
// 循环引用返回同级的新对象
return _parent.currentParent
}
_parent = _parent.parent
}
// 如果obj是一个新对象
if (obj && typeof (obj) === 'object') {
for (let key in obj) {
// 如果字段的值也是一个对象
if (obj[key] && typeof (obj[key]) === 'object') {
result[i] = deepCopy(obj[i], {
//递归执行深拷贝,将同级的待拷贝对象与新对象传递给parent,方便追溯循环引用
originalParent: obj,
currentParent: result,
parent: parent
})
} else {
newObject[key] = obj[key]
}
}
}
return newObject
}
最后看不懂了,先到这里吧
参考文章
https://www.jb51.net/article/140928.htm
https://www.jb51.net/article/181898.htm
https://www.jb51.net/article/192518.htm
发现宝藏前端面试题,等我学成归来
前端面试题之Vue篇 · 语雀