1.JS中基本数据类型有哪几种?null 是对象吗?基本数据和复杂数据类型有什么区别?
基本数据类型:Undefined、Null、Boolean、Number、String、symbol
(1).Undefined类型只有一个值,即特殊的undefined,声明变量但是没有初始化,这个变量的值就是undefined
(2).Null类型只有一个值null,表示一个空对象指针,正式使用typeof操作符检测null会返回object(类似对象)
(3).Boolean有两个字面值:true和false
(4).Number:用来表示整数和浮点数,还有一种特殊的值即NaN,这个数值用来表示一个本来要返回数值的操作数未返回数值的情况(这样就不会抛出错误了)
(5).String类型用于表示由零或多个16位Unicode字符组成的字符序列,即字符串。字符串可以由单引号(')或双引号(")表示。
三大引用类型
1.Object类型
2.Array类型
3.Function类型
存储空间:基本类型存在栈中,引用类型存在栈上
值传递:基本类型传递的是值,引用类型传递的是地址(引用)
作为函数的参数:基本类型传递的是值,引用类型传递得是地址
2.对象类型和原始类型的不同之处?函数参数是对象会发生什么问题?
在 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 分配了一个对象时就出现了分歧,请看下图
3.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 也不是百分之百可信的。
4.类型转换
JS 中类型转换只有三种情况,分别是:
- 1.转换为布尔值
- 2.转换为数字
- 3.转换为字符串 类型转换表格
- 转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
5.四则运算符
加法运算符不同于其他几个运算符,它有以下几个特点:
- 运算中其中一方为字符串,那么就会把另一方也转换为字符串
- 如果一方不是字符串或者数字,那么会将它转换为数字或者字符串
1 + '1' // '11'
true + true // 2
4 + [1,2,3] // "41,2,3" (数组通过 toString 转为字符串 1,2,3,得到结果 41,2,3)
'a' + + 'b' // -> "aNaN" (因为 + 'b' 等于 NaN,所以结果为 "aNaN")
4 * '3' // 12
4 * [] // 0
4 * [1, 2] // NaN
6.比较运算符
- 如果是对象,就通过 toPrimitive 转换对象
- 如果是字符串,就通过 unicode 字符索引来比较
let a = {
valueOf() {
return 0
},
toString() {
return '1'
}
}
a > -1 // true
7.如何正确判断 this?箭头函数的 this 是什么?
- 场景题
function foo() {
console.log(this.a)
}
var a = 1
foo() // 1
const obj = {
a: 2,
foo: foo
}
obj.foo() // 2
const c = new foo() // undefined
- 对于直接调用 foo 来说,不管 foo 函数被放在了什么地方,this 一定是 window
- 对于 obj.foo() 来说,我们只需要记住,谁调用了函数,谁就是 this,所以在这个场景下 foo 函数中的 this 就是 obj 对象
- 对于 new 的方式来说,this 被永远绑定在了 c 上面,不会被任何方式改变 this
箭头函数中的 this(箭头函数没有自己的this,继承外层上下文绑定的this。)
function a() {
return () => {
return () => {
console.log(this)
}
}
}
console.log(a()()())
- 箭头函数其实是没有 this 的,箭头函数中的 this 只取决包裹箭头函数的第一个普通函数的 this。在这个例子中,因为包裹箭头函数的第一个普通函数是 a,所以此时的 this 是 window。另外对箭头函数使用 bind 这类函数是无效的。
call,apply,bind
- this 取决于第一个参数,如果第一个参数为空,那么就是 window。
如果对一个函数进行多次 bind,那么上下文会是什么呢
let a = {}
let fn = function () { console.log(this) }
fn.bind().bind(a)() // Window
// fn.bind().bind(a) 等于
let fn2 = function fn1() {
return function() {
return fn.apply()
}.apply(a)
}
fn2()
可以发现不管函数给 bind 几次,fn 中的 this 永远由第一次 bind 决定,所以结果永远是 window
let a = { name: 'yck' }
function foo() {
console.log(this.name)
}
foo.bind(a)() // => 'yck'
- 可能会发生多个规则同时出现的情况,这时候不同的规则之间会根据优先级最高的来决定 this 最终指向哪里。
首先,new 的方式优先级最高,接下来是 bind 这些函数,然后是 obj.foo() 这种调用方式,最后是 foo 这种调用方式,同时,箭头函数的 this 一旦被绑定,就不会再被任何方式所改变。
this详解基础:juejin.cn/post/684490…