共计 6553 个字符,预计需要花费 17 分钟才能阅读完成。
原始(Primitive)类型
涉及面试题:原始类型有哪几种?null 是对象嘛?
在 JS 中,存在着 6 种原始值,别离是:
boolean
null
undefined
number
string
symbol
首先原始类型存储的都是值,是没有函数能够调用的,比方 undefined.toString()
此时你必定会有疑难,这不对呀,明明 '1'.toString()
是能够应用的。其实在这种状况下,'1'
曾经不是原始类型了,而是被强制转换成了 String
类型也就是对象类型,所以能够调用 toString
函数。
除了会在必要的状况下强转类型以外,原始类型还有一些坑。
其中 JS 的 number
类型是浮点类型的,在应用中会遇到某些 Bug,比方 0.1 + 0.2 !== 0.3
,然而这一块的内容会在进阶局部讲到。string
类型是不可变的,无论你在 string
类型上调用何种办法,都不会对值有扭转。
另外对于 null
来说,很多人会认为他是个对象类型,其实这是谬误的。尽管 typeof null
会输入 object
,然而这只是 JS 存在的一个悠久 Bug。在 JS 的最后版本中应用的是 32 位零碎,为了性能思考应用低位存储变量的类型信息,000
结尾代表是对象,然而 null
示意为全零,所以将它谬误的判断为 object
。尽管当初的外部类型判断代码曾经扭转了,然而对于这个 Bug 却是始终流传下来。
对象(Object)类型
涉及面试题:对象类型和原始类型的不同之处?函数参数是对象会产生什么问题?
在 JS 中,除了原始类型那么其余的都是对象类型了。对象类型和原始类型不同的是,原始类型存储的是值,对象类型存储的是地址(指针)。当你创立了一个对象类型的时候,计算机会在内存中帮咱们开拓一个空间来寄存值,然而咱们须要找到这个空间,这个空间会领有一个地址(指针)。
const a = []
对于常量 a
来说,假如内存地址(指针)为 #001
,那么在地址 #001
的地位寄存了值 []
,常量 a
寄存了地址(指针)#001
,再看以下代码
const a = []
const b = a
b.push(1)
当咱们将变量赋值给另外一个变量时,复制的是本来变量的地址(指针),也就是说以后变量 b
寄存的地址(指针)也是 #001
,当咱们进行数据批改的时候,就会批改寄存在地址(指针)#001
上的值,也就导致了两个变量的值都产生了扭转。
接下来咱们来看函数参数是对象的状况
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) // -> {name: "yck", age: 26}
console.log(p2) // -> {name: "yyy", age: 30}
对于以上代码,你是否能正确的写出后果呢?接下来让我为你解析一番:
- 首先,函数传参是传递对象指针的正本
- 到函数外部批改参数的属性这步,我置信大家都晓得,以后
p1
的值也被批改了 - 然而当咱们从新为
person
调配了一个对象时就呈现了一致,请看下图
所以最初 person
领有了一个新的地址(指针),也就和 p1
没有任何关系了,导致了最终两个变量的值是不雷同的。
typeof VS instanceof
涉及面试题:typeof 是否能正确判断类型?instanceof 能正确判断对象的原理是什么?
typeof
对于原始类型来说,除了 null
都能够显示正确的类型
typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof
对于对象 来说,除了函数都会显示 object
,所以说 typeof
并不能精确判断变量到底是什么类型
typeof [] // 'object'
typeof {} // 'object'
typeof console.log // 'function'
如果咱们想判断一个 对象 的正确类型,这时候能够思考应用 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
你可能不晓得 Symbol.hasInstance
是什么货色,其实就是一个能让咱们自定义 instanceof
行为的货色,以上代码等同于 typeof 'hello world' === 'string'
,所以后果天然是 true
了。这其实也侧面反映了一个问题,instanceof
也不是百分之百可信的。
类型转换
涉及面试题:该知识点常在口试题中见到,相熟了转换规则就不害怕此类题目了。
首先咱们要晓得,在 JS 中类型转换只有三种状况,别离是:
- 转换为布尔值
- 转换为数字
- 转换为字符串
咱们先来看一个类型转换表格,而后再进入正题
转 Boolean
在条件判断时,除了 undefined
,null
,false
,NaN
,''
,0
,-0
,其余所有值都转为 true
,包含所有对象。
对象转原始类型
对象在转换类型的时候,会调用内置的 [[ToPrimitive]]
函数,对于该函数来说,算法逻辑一般来说如下:
- 如果曾经是原始类型了,那就不须要转换了
- 调用
x.valueOf()
,如果转换为根底类型,就返回转换的值 - 调用
x.toString()
,如果转换为根底类型,就返回转换的值 - 如果都没有返回原始类型,就会报错
当然你也能够重写 Symbol.toPrimitive
,该办法在转原始类型时调用优先级最高。
let a = {valueOf() {return 0},
toString() {return '1'},
[Symbol.toPrimitive]() {return 2}
}
1 + a // => 3
四则运算符
加法运算符不同于其余几个运算符,它有以下几个特点:
如果对方含有字符串默认先转换成字符串,其余类型优先转换为数字
- 运算中其中一方为字符串,那么就会把另一方也转换为字符串
- 如果一方不是字符串或者数字,那么会将它转换为数字或者字符串
1 + '1' // '11'
true + true // 2
4 + [1,2,3] // "41,2,3"
如果你对于答案有疑难的话,请看解析:
- 对于第一行代码来说,触发特点一,所以将数字
1
转换为字符串,失去后果'11'
- 对于第二行代码来说,触发特点二,所以将
true
转为数字1
- 对于第三行代码来说,触发特点二,所以将数组通过
toString
转为字符串1,2,3
,失去后果41,2,3
另外对于加法还须要留神这个表达式 'a' + + 'b'
'a' + + 'b' // -> "aNaN"
因为 + 'b'
等于 NaN
,所以后果为 "aNaN"
,你可能也会在一些代码中看到过 + '1'
的模式来疾速获取 number
类型。
那么对于除了加法的运算符来说,只有其中一方是数字,那么另一方就会被转为数字
4 * '3' // 12
4 * [] // 0
4 * [1, 2] // NaN
比拟运算符
- 如果是对象,就通过
toPrimitive
转换对象 - 如果是字符串,就通过
unicode
字符索引来比拟
let a = {valueOf() {return 0},
toString() {return '1'}
}
a > -1 // true
在以上代码中,因为 a
是对象,所以会通过 valueOf
转换为原始类型再比拟值。
This 指向问题:
涉及面试题:如何正确判断 this?箭头函数的 this 是什么
this 的指向,是在调用函数时依据执行上下文所动静确定的。
•函数在浏览器 全局环境中被简略调用(非显式 / 隐式绑定下),
严格模式下 this 绑定到 undefined,
否则绑定到全局对象 window/global;
•在 执行函数时 ,如果函数中的 this 是 被上一级的对象所调用,那么 this 指向就是上一级的对象;否则指向全局环境。
•回调函数(除事件函数):
数组的所有遍历办法 forEach,map,filter,reduce,every,some,flatMap,sort;这些办法均应用了回调函数, 因而在所有应用回调函数的办法中, 所有回调函数中 this 都被指 window,
setInterval,setTimeOut 函数中的回调函数的因为作用域不明 (不晓得在哪里调用) 就会指向 window:
•事件函数中的 this:指向侦听的对象
这里的非凡状况(事件函数)是因为:在函数执行时底层函数调用了 call 和 apply,因而此时的回调函数中的 this 就会被指向绑定的侦听对象上;
•在 定义对象属性 时,obj 对象还没有创立实现;this 仍旧指向 window
•个别 构造函数 new 调用,绑定到新创建的对象上;
•个别由 call/apply/bind 办法显式调用,绑定到指定参数的对象上;
面试技巧:如果把这项放到最初说下个问题多半就是三者区别
•个别由 上下文对象调用,绑定在该对象上;
•箭头函数 中,依据外层上下文绑定的 this 决定 this 指向。
*1:全局环境下的 this*
函数在浏览器全局环境中被简略调用,*ES5*非严格模式下指向 window,*ES6*严格模式下指向 undefined。
function fn1( ) {console.log(this) }
fn1( ) // window
function fn2( ) {'use strict'
console.log(this)}
fn2() // undefined
在执行函数时,如果函数中的 this 是被上一级的对象所调用,那么 this 指向就是上一级的对象;否则指向全局环境。
Var foo = {
bar:10,fn:function( ) {console.log(this)
console.log(this.bar)}
}
***\*var fn1 = foo.fn\****
fn1( ) // ***\* 间接调用 \****,this ***\* 指向 window\****,window.bar => undefined
foo.fn( ) // 通过 foo 调用,this 指向 foo,foo.bar => 10
this.a=3;//this--->window
var b=5;function fn(){
var b=10;console.log(b+this.b);//this--->window
// 这种办法仅限于 ES5, 在 ES6 严格模式中 this 将会变成 undefined
}
fn()
*2、回调函数中的 this*
数组的所有遍历办法 forEach,map,filter,reduce,every,some,flatMap,sort;这些办法均应用了回调函数, 因而在所有应用回调函数的办法中, 除了非凡的状况外 (事件函数), 其余所有回调函数中 this 都被指向 window,setInterval,setTimeOut 函数中的回调函数的因为作用域不明(不晓得在哪里调用) 就会指向 window:
var obj = {fn:function () {// console.log(this);***\*return\**** function ( ) {console.log(this);//this--->window
}
}
}
var fn=obj.fn( );fn( );// 因为是在另外的作用域调用
//return 中回调函数因为相当于 var fn=obj.fn()();是在内部执行所以会指向 window
这里的非凡状况(事件函数)是因为:在函数执行时底层函数调用了 call 和 apply,因而此时的回调函数中的 this 就会被指向 document;
*3、对象中的 this*
在定义属性时,obj 对象还没有创立实现;this 仍旧指向 window
箭头函数指向以后域外的内容
var c=100;var obj={
c:10,
b:this.c,//this--->window 定义属性时,obj 对象还没有创立实现,this 仍旧指向 window
a:function(){
// this;//this--->obj
// console.log(obj.c);console.log(this.c);},
d:()=>{
//this--->window
console.log(this);}
}
// console.log(obj);// obj.d();var obj1=obj;obj=null;obj1.a();
这里 a:function()This.c}中为什么不必 obj 而用 this 呢:因为 obj 的地址值可能扭转;就会找不到这个援用变量 obj 对象;
*4、ES6class 中的 this*
class Box{
a=3;static abc(){console.log(this);//Box 静态方法调用就是通过类名. 办法
// Box.abc();// 尽量不要在静态方法中应用 this
}
constructor(_a){this.a=_a}
play(){
// this 就是实例化的对象
console.log(this.a);// 这个办法是被谁执行的,this 就是谁
}
let b=new Box(10);
b.play();
let c=new Box(5);
c.play();
应用静态方法:就指向 box:相当于 box.abc( )调用该办法;所以指向 box
class Box{
a=3;
static abc(){console.log(this);//Box 静态方法调用就是通过类名. 办法
// Box.abc();// 尽量不要在静态方法中应用 this
}
*5、ES5 中的 this*
function Box(_a){this.a=_a;}
Box.prototype.play=function(){console.log(this.a);//this 就是实例化的对象
}
Box.prototype.a=5;
Box.abc=function(){
//this
// 这样的办法, 等同于静态方法
}
var a=new Box(10);
a.play();
Box.abc();
*6、事件函数中的 this:指向侦听的对象 *
document.addEventListener("click",clickHandler);function clickHandler(e){console.log(this);//this--->e.currentTarget
}
*7、Call apply bind 中的 this:指向绑定的对象 *
function fn(a,b){
this.a=a;//this 如果应用了 call,apply,bind,this 将会被指向被绑定的对象
this.b=b;
return this;
}
var obj=fn.call({},3,5)
var obj=fn.apply({},[3,5])
var obj=fn.bind({})(3,5);
*8、箭头函数中的 this:*指向以后函数外的函数或内容与自带 bind(this)的作用
var obj={a:function(){
document.addEventListener("click",e=>{console.log(this);// 指向事件侦听外函数中的 this/obj
});var arr=[1,2,3];arr.forEach(item=>{console.log(this);//this-->obj
});// 相当于自带 bind(this)的作用
arr.forEach((function(item){}).bind(this));}
}
图中的流程只针对于单个规定。