1.JS分为哪两大类?各有什么特点?如何正确判断类型?
-
原始类型
-
js的原始类型为
string,number,boolean,undefined,null和symbol6种,它们储存的是值,但是平常使用中会发现1.toString()这样的情况,原因是因为此时的1已经不是原始类型而是强制转换成了Number对象类型,而Number对象里面含了toString这个方法,所以可以调用,但实际上这样的函数调用后并不会影响原有的值。 -
此外原始类型中还有一些奇怪的现象:
比如
0.1+0.2!= 0.3,因为js的number类型是浮点类型,它的有效处理精度为17位,在精度大于17的情况下它会取一个近似数。对于0.1以及0.2来说它们的二进制是个无限循环小数,所以它会去取结果的近似数,而二者的近似数相加结果为0.300…04 (中间14个0)。如果要得到正确结果可以先放大倍数计算结果后再缩小相应倍数。typeof null === 'object'所以null不应该是对象吗?事实是不对的,这只是 JS 存在的一个悠久的历史 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。 -
原始类型的判断除了
null之外都可以用typeof判断
-
-
对象类型
除了上所说的原始类型,剩下的就是对象类型。
-
对象类型有
String,Number,Boolean,RegExp,Date,Function,Object,Array,Math,Error等内置对象,也有DOM,BOM这样的宿主(浏览器)对象 -
对象储存的不是值,而且指针。对于表达式
const a = {},我们创建了一个对象并赋予变量a,计算机内部则是在内存中分配了空间给改对象,并将内存地址存放于a,此后在使用变量a的时候计算机就能找到对应内存地址并进行相应的操作。 然后对于表达式const b = a,其实就是把a所指向的地址复制给了b,也就是同一个对象,当对b进行一些修改操作后会发现a也发生了同样的变化。所以对于对象类型的操作要小心小心。
延伸:对象拷贝。
- 在js中对象拷贝分为两种,深拷贝跟浅拷贝
- 浅拷贝可以通过
Object.assign来解决这个问题,很多人认为这个函数是用来深拷贝的。其实并不是,Object.assign只会拷贝所有的属性值到新的对象中,如果属性值是对象的话,拷贝的是地址,所以并不是深拷贝const a = {b:1,c:2,d:{e:4}} const b = Object.assign({}, a) // a !== b 但是a跟b中的d属性是相等的,他们指向同一个地址 - 正常情况下,浅拷贝能解决大部分问题,但是如果这个时候需要修改d属性,那么就需要用到深拷贝,对于深拷贝,目前通常使用
JSON.parse(JSON.stringify(object)),不过该方法也有局限性:会忽略 undefined、symbol,不能序列化函数,不能解决循环引用的对象。那怎么办?没有关西,这里还有种解决方案MessageChannel,它可以解决上面方法中的大部分问题。MessageChannel开辟了一个通信通道,通道两头为两个端口,每个端口都能发送和接受消息:
然而,大部分不是所有,它无法拷贝带有函数的对象=_=. 那就没完美的解决方案了吗?您好。有的!这里就有现成的轮子lodash 的深拷贝函数,或者如果你想自己实现一个深拷贝轮子并且考虑好多种边界情况,比如原型链如何处理、DOM 如何处理等,那么完全可以尝试下突破自我~var channel = new MessageChannel(); var port1 = channel.port1; var port2 = channel.port2; port1.onmessage = function(event) { console.log("port1收到来自port2的数据:" + event.data); } port2.onmessage = function(event) { console.log("port2收到来自port1的数据:" + event.data); } port1.postMessage("发送给port2"); port2.postMessage("发送给port1");
如何判断对象类型呢
- 无法使用
typeof精确判断对象类型,除了函数,其他对象类型的结果为object,用Array.prototype.slice.call(obj)是一种方法 - 对于对象类型,除了上面的方法,还可以使用
instanceof,它的内部机制是通过原型链来判断。// a instanceof b function _instanceof(a, b) { if (a === null || typeof a !== 'object') return false let proto = a.__proto__ while(proto) { if (proto === b.prototype) return true proto = proto.__proto__ } return false } - 但是
instanceof也不是百分百可以判断的,在class中,我们可以通过静态方法[Symbol.hasInstance]自定义instanceof在某个类上的行为, 因此我们也可以使用该方式去实现用instanceof判断原始类型的
方法。class PrimitiveString { static [Symbol.hasInstance](x) { return typeof x === 'string' } } console.log('hello world' instanceof PrimitiveString) // true
-
2. 你理解的原型是什么?
-
行走江湖,不会点武功本事怎么行,这个高手会很多武功,他可以收徒弟,并将这些武功传授给他,学不会没关系,还可以记在身体上,到需要时能想起来就行,就像天下第一里的成是非…而徒弟不仅学会了师傅的武功,也有自己的际遇学会其他厉害的武功,他也可以继续收徒弟传授下去…可以说你强就是我强,青出于蓝胜于蓝!
-
在js中所有的函数默认都有一个公有并且不可枚举的对象属性
prototype,该对象默认的只有一个叫做constructor的属性,指向这个函数本身。不仅如此,每个对象都有一个隐藏的属性——__proto__,这个属性引用了创建这个对象的函数的prototype,也就是说function A() { //... } const a = new A() a.__proto__ == A.prototype -
那么
__proto__这个属性是干什么用的呢?当我们访问对象a中的某个属性,首先会在a中查找是否存在这个属性,如果没有,就去a的__proto__中查找,如果有则返回a.__proto__中的这个属性,但是如果还是没有,那么继续从a.__proto__.__proto__中查找,一直到查到目标属性或者之后的__proto__不存在为止,而这个查找链路就是原型链。延伸:原型继承, class 继承 -
不过,也不是所有函数都有
prototype属性,在规范中有这么段描述:Built-in functions that are not constructors do not have a prototype property unless otherwise specified in the description of a particular function..非构造函数的内置函数没有prototype属性,除非在特定函数的描述中另有说明。prototype存在的意义是在 function 作为 constructor 用时(new 或 super)能复制到生成对象的__proto__上。对于一些内部方法明确是不会作为 constructor 的,所以没有 prototype 是很合理的,比如迭代器,箭头函数,bind返回的函数
3. call, apply, bind各自有什么区别
三个都是为了修改this指向
- 功能上来说他们没什么区别, call、apply会在函数调用后马上执行,同时它们两个接受参数的形式不同,call除了第一个指定的this外,接受的参数跟方法体一一对应,而apply则是只有两个参数,第一个跟call一样,第二个则是一个参数数组。至于bind,它的参数形式跟call一样,只不过它返回的是原函数,需要再执行一次,同时,因为返回的是个函数,所以它可以调用的时候再传参数。
- 在某些情况下可以通过类似that这样的变量缓存this,并通过该变量去使用。
延伸:this绑定
-
默认绑定:非严格模式下全局环境调用函数执行,this指向window
function foo() { "use strict" console.log(this.a) } var a = 1 foo() // TypeError: this is undefined -
隐式绑定:this总是指向调用该函数的对象
function foo() { console.log( this.a ); } var obj2 = { a: 42, foo: foo }; var obj1 = { a: 2, obj2: obj2, foo: foo }; obj1.foo() // 2 obj1.obj2.foo(); // 42不过要注意以下隐式丢失的情况:
function foo() { console.log( this.a ); } function baz(fn) { fn() } var obj = { a: 2, foo: foo }; var bar = obj.foo; // 函数别名!此时的bar是foo的函数引用 var a = "global"; bar() //global 此时的bar()相当于是 foo() 以下也是 barz(obj.foo) setTimeout(obj.foo, 100) /* ↓ 相当于 function setTimeout(fn, delay) { // delay fn() } */ -
显式绑定
前面说到的call,apply,bind -
new绑定
使用new来调用函数会发生以下操作:
- 创建一个全新的对象。
- 对新对象执行原型链设置。
- 绑定函数调用的this为该新对象。
- 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
function new(fn, ...args) { const obj = Object.create(fn.prototype) let result = Parent.apply(child, args); return typeof result === 'object' ? result : obj; } -
箭头函数
严格来说箭头函数它没有this,它的this来自外层(函数或者全局)作用域,它跟普通变量一样会向上逐级寻找,所以改变作用域中的this也会改变箭头函数中的。

25万+

被折叠的 条评论
为什么被折叠?



