JS 数据类型

164 阅读12分钟

1.数据类型

基础类型:string、number、boolean、null、undefined、symbol

引用类型:对象(object)、数组(Array)、函数(Function)、日期(Date)、正则表达式(RegExp)、Map、Set

基本类型和引用类型的区别

基本类型的值存储在栈中。当我们把a赋值给b的时候(b = a),会开辟一块新的空间存储b的值。今后即使修改a的值,b也不会改变。

引用类型存储的是对象的地址。假设把obj1赋值给obj2,只要我们修改其中一个变量所引用的对象时,其他引用该对象的变量也会受到影响。

JS有哪些内置对象

String,Boolean 、 Number、Array 、Object、Function、Math、Date、RegExp,

常用的 

Math abs() ,sqrt() max() min()

Date new Data() getYear

Array  

String  concat() lenth slice() split()

说一下call,apply。bind 的区别

共同点:功能一致 可以改变this指向 语法:函数.call() , 函数.apply(), 函数.bind()

区别:

  1. call,apply可以立即执行。bind不会立即执行,因为bind返回的是一个函数需要加入()执行

  2. 参数不同:apply 第二个参数是数组。call 和bind 有多个参数需要挨个写

数据类型判断 typeof / instanceof / === /constructor()

typeof 返回数据类型的字符串,

可以判断:undefined/number/string/boolean/函数(Function)

不可以判断 null与Object、Object与Array

var a 
console.log (a,typeof a === 'undefined',a === undefined) // undefined,true,true
console.log(undefined === 'undefined') // false

a = 4
console.log(typeof a === 'number')

a = 'wangjianguo'
console.log(typeof a === 'string')

a = true
console.log(typeof a === 'boolean')

a = null
console.log(typeof a ,a === null) // 'object',true

console.log(typeof NaN ) // Number

instanceof 判断对象的具体类型

var b1 = {
    b2:[1,'abc',console.log],
    b3:function () {
        console.log('b3')
    }
}

console.log(b1 instanceof Object,b1 instanceof Array)// true false
console.log(b1.b2 instanceof Array,b1.b2 instanceof Object)// true true
console.log(b1.3 instanceof Function,b1.b3 instanceof Object)// true true

console.log(typeof b1.b3,typeof b1.b3 === 'function') // 'function' true

console.log(typeof b1.b2[2] === 'function') // true

constructor() // 几乎可以判断基本数据类型和引用数据类型;如果声明了一个构造函数,并把它的原型指向了Array。

console.log(‘abc’ constructor === String) // true
Object.prototype.toString.call()

JS判断变量是不是数组,你能写出几种方法(判断虚拟dom 有没有子元素 children 是否为数组)

    var str = ‘你好’

    var arr = [1,2,3]

    方式1:console.log(Array.isArray(arr)) // true

    方式2:console.log(arr instanceof Array) // true

    方式3:console.log(Object.prototype.toString.call(arr)) // [object Array]

    方式4:console.log(Array.prototype.isPrototypeOf(arr)) // true

    方式5:console.log(arr.constuctor.toString()) // true

如何判断一个变量是数组还是对象?(不能用typeof)

let arr = []
let obj = {name:"Jack"}

console.log(typeof arr) // 输出object
console.log(typeof obj) // 输出object

1.可以使用Object.prototype.toString.call()来判断数组和对象,当我们传入不同的参数,其返回结果不同 对于数组,返回的结果是“[object Array]” 对于对象,返回的结果是“[object Object]”

2.Array.isArray()

Array.isArray(arr) // 如果数组返回true,不是返回false

3.null 是不是对象类型?

console.log(typeof null) // "object"

为什么 type of null 是 object

这是因为在JavaScript的底层实现中,它们使用了一种称为“标签”的机制来存储不同类型的值。对于对象类型,其标签值的二进制表示的低三位都是0。而是null值在内存中被表示为全0,因此其低三位也都是0。这导致typeof 操作符将null错误地识别为对象类型。

因此,在javaScript中当你需要检查一个值是否为null时,最好直接使用 value === null,而不是依赖 typeof 操作符。

null是一种基本类型,存储在栈区;而typeof null的结果却是Object,而typeof null的结果却是Object,而Object是引用类型,存储在堆区。其次,根据代码console.log(null instanceof Object)输出结果为false,我们可以知道null并不是Object的实例,两者之间存在矛盾。

isNaN 和 Number.isNaN

isNaN接收参数后,会尝试将这个参数进行类型转换。如果不能被转化为Number类型,则会返回true,因此非数字值传入也会返回true,会影响NaN的判断。

函数Number.isNaN不会进行数据类型的转换,这种方法对于NaN的判断更为准确。

console.log(Number.isNaN("Hello")) // Output:false
console.log(isNaN("Hello")) //  Output:true

console.log(Number.isNaN(NaN)) // Output:true
console.log(isNaN(NaN)) // Output:true

console.log(Number.isNaN(123)) //  Output:false
console.log(isNaN(123)) // Output:false

4.判断数组的方式 通过原型链 arr.__proto === Array.prototype

console.log(arr instanceof Array)

类型转换

  1. 显示类型转换 显式类型转换是指程序员手动调用函数进行类型转换。 JavaScript 提供了 一些内置的函数(如 Number(),String(),Boolean()等)来进行显式类型转换。
let num = 42
let str = String(num)
console.log(str) // 输出 “42”

let num = 42
let str2 = num2.toString()
console.log(str2) // 输出 “42”

let str3 = "42"
let num3 = Number(str3)
console.log(num3) // 输出 42

let bool = true 
let num4 = Number(bool)
console.log(num4) // 输出 1

let num5 = 42 
let bool2 = Boolean(num5)
console.log(bool2) // 输出 true

let str4 = ""
let bool3 = Boolean(str4)
console.log(bool3) // 输出 false

let str5 = "42"
let num6  = parseInt(str5)
console.log(num6) // 输出 42

let hex = "FF";
let num7 = parseInt(hex,16)
console.log(num7) // 输出 255

2.隐式类型转换

隐式类型转换指的是,在我们没有调用类型转换函数的时候,JavaScript自动帮我们进行类型转换 常见的隐式类型转换

1.比较操作( == )
  • 如果两个操作数类型相同,执行严格比较

  • 如果两个操作数类型不同,则进行类型转换后再进行比较。规则如下:

    1. 如果一个操作数是数值(number),另一个操作数是字符串,则将字符串转换为基本类型,然后进行比较。
     console.log("123" == 123) // 输出 true
     console.log("3.14" == 3.14) // 输出 true
    
    1. 如果一个操作数是布尔值,则将布尔值转换为数值,然后进行比较。
     console.log(true == 1) // 输出 true
     console.log(false == 0) // 输出 true
    
    1. 如果一个操作数是对象,另一个操作数是数值或字符串,会首先调用对象的valueOf()方法,将对象转化为基本类型,再进行比较。当valueOf()返回的不是基本类型的时候,才会调用toString()方法。
    let myObject = {
        valueOf(){
            return 1
            // return {} 当valueOf()返回的不是基本类型的时候,才会调用toString()方法。
        },
        toString(){
            return "2"
        }
    }
     console.log(myObject == "2") // 输出 false
     console.log(false == 1) // 输出 true
    
    let a = {
        value:1,
        valueOf:function () {
            return this.value++;
        },
        toString:function () {
            return this.value++;
        }
    }
    
    if (a == 1 && a == 2 && a ==3){
        console.log("条件语句返回值为true")
    }
    
2.四则运算(加减乘除)
加法运算规则
  1. 当两个操作数都是数字时,执行常规的数字相加
    console.log(3 + 4) // 输出 7
    
  2. 当两个操作数都是字符串时,执行字符串连接
   console.log('Hello' + 'World!') // 输出 "HelloWorld!"
  1. 当一个操作数是数字,另一个操作数是字符串时,数字会先被转换为字符串,然后执行字符串连接
   console.log(3 + '4') // 输出 "34"
   console.log("4" + 3) // 输出 "34"
  1. 当一个操作数是对象时,对象会根据之前解释的转换为基本类型值(字符串或数字),然后按照前面的规则执行加法运算
  let myObject = {
      valueOf(){
          return 42
          // return {} 当valueOf()返回的不是基本类型的时候,才会调用toString()方法。
      },
      toString(){
          return "I am an object"
      }
  }
   console.log(obj + 1) // 输出 "43"
   console.log("Hello" + obj) // 输出 "Hello42"
  1. 当一个操作数是布尔值,布尔值会先被转换为数字(true 被转换为1,false被转换为0),然后按照前面的规则执行加法运算
   console.log(false + '4') // 输出 "false4"
   console.log(true + 3) // 输出 4 (true 被转换为数字1)
  1. 当一个操作数是null或undefined时,null和undefined分别被转换为数字0和NaN,按照按照前面的规则执行加法运算。
   console.log(null + 2) // 输出 2 (null 被转换为数字0)
   console.log(undefined + 2) // 输出 NaN (undefined 被转换为数字NaN)
   console.log('Hello,' + null) // 输出 "Hello,null" (null 被转换为字符串“null”)
   console.log('Hello,' + undefined) // 输出 "Hello,undefined" (undefined 被转换为字符串 “undefined”)
其他运算符(减、乘、除)
减法运算符的规则
console.log(5 - '2') //输出 3 (‘2’ 被转换为数字 2)
console.log('5' - 2) //输出 3 (‘5’ 被转换为数字 5)
console.log(true - 2) //输出 -2 (true 被转换为数字 1)
consolelog(null - 2) // 输出 -2 (null 被转换为数字 0)
console.log(undefined - 2)//输出NaN (undefined 被转换为数字NaN)
乘法运算符的规则
console.log(5 * '2') //输出 10 (‘2’ 被转换为数字 2)
console.log('5' * 2) //输出 3 (‘5’ 被转换为数字 5)
console.log(true * 2) //输出 2 (true 被转换为数字 1)
consolelog(null * 2) // 输出 0 (null 被转换为数字 0)
console.log(undefined * 2)//输出NaN (undefined 被转换为数字NaN)
除法运算符的规则
console.log(10 / '2') //输出 5 (‘2’ 被转换为数字 2)
console.log('10' / 2) //输出 5 (‘5’ 被转换为数字 5)
console.log(true / 2) //输出 0.5 (true 被转换为数字 1)
consolelog(null / 2) // 输出 0 (null 被转换为数字 0)
console.log(undefined / 2)//输出NaN (undefined 被转换为数字NaN)
3.在条件语句(比如if)后面的表达式

以下类型会被转化为false,其他类型会被转化为true

  • false
  • 0
  • NaN
  • null
  • undefined
  • 空字符串(“”)
if (0) {
    console.log("不执行代码块")
}// 0 转换为false,因此不执行代码块

if (1) {
    console.log("执行代码块")
}// 1转换为true,因此执行代码块

let emptyString = “”
if (emptyString) {
    console.log("不执行代码块")
}// 空字符串转换为true,因此不执行代码块

let nonEmptyString = “Hello”
if (nonEmptyString) {
    console.log("执行代码块")
}// 非空字符串转换为true,因此执行代码块

let nullValue = null
if (nullValue) {
    console.log("不执行代码块")
}// null转换为false,因此不执行代码块

let objectValue = {}
if (objectvalue) {
    console.log("执行代码块")
} // 对象转化为true,因此执行代码块
null 和undefined的区别

undefined 代表定义未赋值,null定义了并赋值为空

var a 
console.log(a) // undefined
a = null
console.log(a) // null
if (!undefined){
    console.log("undefined is false")
}// undefined is false

if (!null){
    console.log("null is false")
}// null is false

undefined == null // true

javaSctipt 最初设计时的考虑 null 是一个表示“无”的对象,转换为数值时为0,undefined是一个表示“无”的原始值,转为数值时为NaN。

项目实践中的区别 null表示“没有对象”,即该处不应该有值。典型用法是:

  • 作为函数的参数,表示该函数的参数不是对象。
  • 作为对象原型链的终点。

undefined表示“缺少值”,就是此处应该有一个值,但是还没有定义。典型用法是: 1.变量被声明了,但没有赋值时,就等于undefined。 2.调用函数时,应该提供的参数没有提供,该参数等于undefined。 3.对象没有赋值的属性,该属性的值为undefined。 4.函数没有返回值,默认返回undefined。

1.作者在设计js 的都是先设计的null(为什么设计了null:最初设计js 的时候借鉴了java的语言)

2.null会被隐式转换为0,很不容易发现错误。

3.先有null后有undefined,出来undefined是为了填补之前的坑。

具体区别:JavaScript的最初版本是这样的的:null是一个表示“无‘的对象(空对象指针),转为数值时为0,undefined,转为数值时为NaN。

数组的索引

函数、对象、数组,number 作为数组索引的时候,会被转化为字符串,symbol作为索引时类型不会被转化,可以直接作为索引。

let a = new Array()
a[0] = 1
a["0"] = 2
a[0]++
console.log("a[0]的输出是",a["0"]) // 3
console.log("a[0]的输出是",a[0]) //3
let obj = []
let key = {a:1};
obj[key] = "value"
console.log(obj["[object Object]"])
let arr = []
function myFunction() {
    return "wangjianguo"
}
arr[myFunction] = "函数作为索引"
console.log(arr[myFunction]) // "函数作为索引"
浅拷贝

浅拷贝是一种复制对象的方式,它创建一个新的对象,并将原始对象的属性值复制到新对象中。但是,如果属性值是对象或数组。浅拷贝只会复制它们的引用而不是创建副本。换句话说,浅拷贝只复制了对象的表面层级,而不会递归复制内部的嵌套对象。

简单来说,浅拷贝只复制了对象的一层,如果原始对象中的属性值是对象,那么拷贝后的对象与原始对象共享相同的引用,修改其中一个对象的属性值会影响到另一个对象。

浅拷贝:只复制引用,而未复制真正的值,

var arr1 = ['a','b','c','d']
var arr2 = arr1
arr1[0] = '你好吗'

var obj1 = {a:1,b:2}
var obj2 = Object.assign(obj1)
obj1.a = '100'
obj2.b = '你怎么样'
console.log(obj1,obj2)

深拷贝:是复制真正的值(不同引用)

originalObj = {
    name:'John',
    age:30,
    hobbies:['reading','coding']
}

// 浅拷贝对象
const shallowCopyObj = Object.assign({},originalObj)

//修改拷贝对象的属性
shallowCopyObj.name = 'Alice'
shallowCopyObj.hobbies.push('painting')

console.log(originalObj) // {name:'John',age:30,hobbies:['reading','coding','painting']}

console.log(shallowCopyObj) // {name:'Alice',age:30,hobbies:['reading','coding','painting']} 

对象的浅拷贝方式有哪些?

const obj1 = {a:1,b:2,c:{d:4}}
// 方式1
const obj2 = Object.assign({},obj1)

// 方式2 展开运算符
const obj3 = {...obj1}

// 方式3
const obj2 = Object.assign({},obj1)
for (const key in obj1){
    //  在遍历obj1 属性时,不光会遍历自己的属性还会遍历原型链上的属性,hasOwnProperty 用来判断是不是自己的属性
    if (obj1.hasOwnProperty(key)){
        obj2[key] = obj1[key]
    }
}

// 方式4
// Object.keys 只会有自己的属性
Object.keys(obj1).forEach((key) =>{
    obj2[key] = obj1[key]
})

console.log(obj2) // {a:1,b:2}
console.log(obj3) // {a:1,b:2}
console.log(obj4)

数组的浅拷贝方式有哪些?

// 方式1
const arr1 = [1,2,3]
// slice 截取数组,不传就是 全部拷贝
const arr2 = arr1.slice()
cosole.log(arr2) // [1,2,3]

// 方式2
const arr3 = [].concat(arr1)
cosole.log(arr3) // [1,2,3]

深拷贝

const obj = {
    name:'John',
    age;30,
    address:{
        city:'New York',
        country:'USA'
    }
}

const deepCopy = JSON.parse(JSON.stringify(obj))
console.log(deepCopy) // {name:'John',age;30,address:{city:'New York',country:'USA'}}

//修改原对象
obj.name = 'Alice'
obj.address.city = 'San Francisco'

console.log(obj)// 输出 {name:'Alice',age;30,address:{city:'San Francisco',country:'USA'}}
console.log(deepCopy) //输出 {name:'John',age;30,address:{city:'New York',country:'USA'}}
JSON.parse(JSON.stringify(obj)) 的问题
通过序列化的时候,会忽略函数,是不能拷贝函数的
const obj = {
    a:1,
    b:function () {
        console.log('hello')
    }
}

const newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj) // {a:1}
无法拷贝特殊对象,如 Date、正则表达式
const obj = {
    date:new Date(),
    regex:/test/
}

const newObj =  JSON.parse(JSON.stringify(obj))
console.log(newObj.date) // 输出字符串,而不是Date对象
console.log(newObj.regex) // 输出空对象,而不是RegExp对象
不会拷贝原型链上的属性
const protoObj = {c:3}
cosnt obj = Object.create(protoObj)//obj 的原型对象是protoObj
obj.a = 1
obj.b = 2

const newObj = JSON.parse(JSON.stringify(obj))
console.log(obj.c) // 3
console.log(newObj.c) // undefined
会忽略symbol 和 undefined 属性
const obj = {
    a:undefined,
    b:Symbol('test')
}
const newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj) // 输出空对象:{}
深拷贝函数
function copyObj(obj){
    var newObj = Array.isArray(obj) ? [] : {}
    for (var key in obj){
        if (typeof obj[key] == 'object'){
            newObj[key] = copyObj(obj[key])
        }else {
            newObj[key] = obj[key]    
        }
    }
    return newObj
}