原始类型有哪几种?null 是对象嘛?基本数据类型和引用数据类型在存储上的区别?
六种原始数据类型(原始类型存储的都是值,是没有函数可以调用的),"1".toString() 为什么能够调用,因为"1"会强制转换为String类型也就是对象类型(装箱操作)。 JS 的 number 类型是
浮点类型
的,在使用中会遇到某些 Bug,比如 0.1 + 0.2 !== 0.3
- string
- number
- boolean
- null
- undefined
- symbol(es6新增)
null也并不是对象,虽然typeof null 为object。但是这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object
基本数据类型是存储在栈中的值,而引用数据类型是存在栈中的引用,查询的时候通过这个栈中的引用来查找对应中的堆中的具体的值。
0.1 + 0.2 !== 0.3
想要知道为什么0.1+0.2!=0.3 ,首先知道计算机用位来储存及处理数据,每一个二进制数(二进制串)都一一对应一个十进制数。十进制整数转二进制方法:除2取余;十进制小数转二进制方法:乘2取整。所以十进制0.1转化为2进制乘以2的过程就是
0.1 * 2 = 0.2 取 0 0.2 * 2 = 0.4 取 0 0.4 * 2 = 0.8 取 0 0.8 * 2 = 1.6 取1 0.6 * 2 = 1.2 取 1 0.2 * 2 = 0.4 取 0 所以0.1的二进制格式就是0.00011 无线循环,但计算机内存有限 在某个精度点直接舍弃。当然,代价就是,0.1在计算机内部根本就不是精确的0.1,而是一个有舍入误差的0.1 所以一开始计算之前0.1 就是不准确的 所以最终的结果0.1+0.2!=0.3
对象类型和原始类型的不同之处?函数参数是对象会发生什么问题?
原始类型存储的是具体的值,对象类型存储的是地址的引用(指针)。
参数是对象的情况。
function test(person) { person.age = 26 person = { name: 'yyy', age: 30 } return person } const p1 = { name: 'yck', age: 25 } const p2 = test(p1) console.log(p1) // -> ? console.log(p2) // -> ?复制代码
过程如下图:
typeof 是否能正确判断类型?instanceof 能正确判断对象的原理是什么?
- typeof对于原始值,除了null都能判断出正确的类型。
- typeof对于对象来说,除了函数都会显示object,所以说typeof不能准备判断类型
- instanceof可以判断一个实例是否属于当前类,因此可以判断类型
const Person = function() {} const p1 = new Person() p1 instanceof Person // true var str = 'hello world' str instanceof String // false var str1 = new String('hello world') str1 instanceof String // true //对于原始类型来说,你想直接通过 instanceof 来判断类型是不行的,当然我们还是有办法让 instanceof 判断原始类型的 class PrimitiveString { static [Symbol.hasInstance](x) { return typeof x === 'string' } } console.log('hello world' instanceof PrimitiveString) // true复制代码
类型转换
- undefined, null, false, NaN, '', 0, -0 为false外,其余都为true
- 对象转换为原始类型会调用valueof()和toString()(不同的运算两者的顺序还会有所不同),当然你也可以重写 Symbol.toPrimitive ,该方法在转原始类型时调用优先级最高。
let a = { valueOf() { return 0 }, toString() { return '1' }, [Symbol.toPrimitive]() { return 2 } } 1 + a // => 3复制代码
- 'a'++'b' // 'aNaN' ,因为 + 'b' 等于 NaN,所以结果为 "aNaN",你可能也会在一些代码中看到过 + '1' 的形式来快速获取 number 类型
- 比较运算符
- 如果是对象,就通过 toPrimitive 转换对象
- 如果是字符串,就通过 unicode 字符索引来比较
let a = { valueOf() { return 0 }, toString() { return '1' } } a > -1 // true复制代码
this指向问题
function foo() { console.log(this.a) } var a = 1 foo() const obj = { a: 2, foo: foo } obj.foo() const c = new foo()复制代码
- foo() 无论在什么环境下执行,都是window
- obj.foo() 看是谁调用的foo ,this就是谁(也就是obj)
- new 出来的this 指向当前实例(也就是c)
- 箭头函数其实是没有 this 的,箭头函数中的 this 只取决包裹箭头函数的第一个普通函数的 this
- 改变this的指向(call、apply、bind),bind多次的this只取决于第一次的bind。
// 自己实现bind Function.prototype.testBind = function(that){ var _this = this, /* *由于参数的不确定性,统一用arguments来处理,这里的arguments只是一个类数组对象,有length属性 *可以用数组的slice方法转化成标准格式数组,除了作用域对象that以外, *后面的所有参数都需要作为数组参数传递 *Array.prototype.slice.apply(arguments,[1])/Array.prototype.slice.call(arguments,1) */ slice = Array.prototype.slice, args = slice.apply(arguments,[1]); //返回函数 return function(){ //apply绑定作用域,进行参数传递 return _this.apply(that,args) } }复制代码
- this的优先级(你不知道的js很清楚了讲了这个优先级的问题,这里列一下,从高到低)
- new 方式
- bind等函数
- obj.foo()
- foo()
== 和 === 有什么区别?([]==![],![] 转换为false,左边先转换为字符串"",最后两者都转换为0)
- == 会进行隐式的类型转换(简单的理解最终两个类型不相同会转换为数字进行比较,如果到字符串级别可以比较了就不会再向下进行转换)
- 判断两者类型是否为 string 和 number,是的话就会将字符串转换为 number
- 判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断
- 判断其中一方是否为 object 且另一方为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进行判断
- 对于 === 来说就简单多了,就是判断两者类型和值是否相同。
闭包(经典)(函数能够记住
并访问其所在的词法作用域
(你不知道的js书中解释))
函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包。
经典面试题:循环中使用闭包解决 var
定义函数的问题
for (var i = 1; i <= 5; i++) { setTimeout(function timer() { console.log(i) }, i * 1000) } // 6 6 6 6 6 // 解决方法1:闭包 for (var i = 1; i <= 5; i++) { ;(function(j) { setTimeout(function timer() { console.log(j) }, j * 1000) })(i) } // 解决方法2:setTimeout 的第三个参数,这个参数会被当成 timer 函数的参数传入 for (var i = 1; i <= 5; i++) { setTimeout( function timer(j) { console.log(j) }, i * 1000, i ) } // 解决方法3:let for (let i = 1; i <= 5; i++) { setTimeout(function timer() { console.log(i) }, i * 1000) }复制代码
深浅拷贝及其实现
- 浅拷贝
- Object.assign()
- es6的扩展运算符(...)
- 深拷贝
- JSON.parse(JSON.stringify(object)),但是有局限性。
- 会忽略 undefined
- 会忽略 symbol
- 不能序列化函数
- 不能解决循环引用的对象
- JSON.parse(JSON.stringify(object)),但是有局限性。
let a = { age: undefined, sex: Symbol('male'), jobs: function() {}, name: 'xxx' } let b = JSON.parse(JSON.stringify(a)) console.log(b) // {name: "xxx"}复制代码
- 如果你所需拷贝的对象含有内置类型并且不包含函数,可以使用 MessageChannel
function structuralClone(obj) { return new Promise(resolve => { const { port1, port2 } = new MessageChannel() port2.onmessage = ev => resolve(ev.data) port1.postMessage(obj) })}var obj = { a: 1, b: { c: 2 }}obj.b.d = obj.b// 注意该方法是异步的// 可以处理 undefined 和循环引用对象const test = async () => { const clone = await structuralClone(obj) console.log(clone)}test()复制代码
// 自己实现的deepCopy function deepClone(obj) { function isObject(o) { return (typeof o === 'object' || typeof o === 'function') && o !== null } if (!isObject(obj)) { throw new Error('非对象') } let isArray = Array.isArray(obj) let newObj = isArray ? [...obj] : { ...obj } Reflect.ownKeys(newObj).forEach(key => { newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key] }) return newObj }复制代码
原型
- Object 是所有对象的爸爸,所有对象都可以通过 proto 找到它
- Function 是所有函数的爸爸,所有函数都可以通过 proto 找到它
- 函数的 prototype 是一个对象
- 对象的 proto 属性指向原型, proto 将对象和原型连接起来组成了原型链