1.1、变量和类型(JavaScript基础)

123 阅读8分钟

一、JavaScript 规定了几种语言类型

两种类型:基本类型和引用类型

基本类型(7种):

String、Number、Boolean、Null、Undefined、Symbol、BigInt

访问:基本类型数据的值按值访问(直接在Stack中取值)

存储:基本类型的数据存放在栈内存(Stack)中

理解一下我不了解的 -> Null && Undefined

  • Null 类型只有一个值:null(mdn解释),null 表示一个空值,变量赋值了,只不过赋值为空 转换为数字时:
console.log(Number(null))    // 0
  • 一个没有被赋值的变量会有个默认值 undefined(mdn解释),表示变量没有赋值 转换为数字时:
console.log(Number(undefined))  // NaN

两者比较:

console.log(null == undefined)    // true
console.log(null === undefined)   // false

小意外

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

Null是基本数据类型,为什么打印出来会是object?

据说这是js由来已久的bug,js的数据在底层是以二进制存储,比如null所有存储值都是0,底层的判断机制,只要前三位为0,就会判定为object,所以才会有 typeof null === 'object'


引用类型(1种):Object

Object又包含了 ①Object类型 ②Function类型 ③Array类型 ④RegExp类型 ⑤Date类型 ⑥基本包装类型 ⑦单体内置对象

理解一下我不了解的 -> 基本包装类型 && 单体内置对象

  • 基本包装类型:js提供了三个特殊的引用类型(Boolean\Number\String), 为了方便操作基本类型值,每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象,从而能够调用一些方法来操作这些数据。例如字符串可以直接.length,调用 charAt(),slice()等,数字可以直接调用toString(),toFixed()等
  • 单体内置对象:是ECMAScritp提供的、不依赖于宿主环境的对象,不需要通过new来实例化,可以直接使用。例如Global的decodeURI()、decodeURIComponent(),例如直接Math.各种方法

二、JavaScript 对象的底层数据结构是什么

JavaScript 存储数据使用的是栈(Stack)和堆(Heap)

基本数据类型是值直接存储在栈中,访问直接到栈中取

引用数据类型值存放在堆中,栈中存放着指向值的地址,访问先到栈中取地址,再根据地址到堆中找到数据

const s1 = 'string'
const s2 = 777
const s3 = { key: 'value' }
const s4 = [1]

三、Symbol 类型在实际开发中的应用、可手动实现一个简单的Symbol

mdn解释:symbol是一种基本数据类型。Symbol()函数会返回symbol类型的值,具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;静态方法会暴露全局的symbol注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持 new Symbol() 语法。

每个从Symbol返回的symbol值都是唯一的。一个symbol值能作为对象属性的标识符,这是该数据类型仅有的目的。

我理解一下,Symbol()返回一个独一无二的值,不能通过new调用,可以作为对象属性的标识

const s1 = Symbol()
console.log(s1)              // Symbol()
console.log(typeof s1)       // symbol
const s2 = Symbol('s2')
console.log(s2)              // Symbol(s2)
const s3 = Symbol('s2')
console.log(s2)              // Symbol(s2)
s2 === s3                    // false
const s4 = Symbol()
const s5 = { [s4]: 's4' }
console.log(s5[s4])          // 's4'
// 可以s5[s4]调用,不能s5.s4调用

四、基本类型对应的内置对象,以及他们之间的装箱拆箱操作

上文中提到过的基本包装类型(Boolean\Number\String)就是基本类型对应的内置对象。

装箱:把基本数据类型转化为对应的引用数据类型,装箱分为隐式装箱和显式装箱。《javascript高级程序设计》中描述每当读取一个基本类型的时候,后台就会创建一个对应的基本包装类型对象,从而让我们能够调用一些方法来操作这些数据。

let s1 = 'Hello, JavaScript'
console.log(s1.slice(7, 17))       // JavaScript
let n2 = 5.67
console.log(n2.toFixed(1))         // 5.7

可以看出虽然是基本数据类型,但是可以调用方法,就是因为js在后台进行了隐式装箱动作,上面代码运行时实际为

let s1 = 'Hello, JavaScript'
console.log(new String(s1).slice(7, 17))   // JavaScript
let n2 = 5.67
console.log(new Number(n2).toFixed(1))     // 5.7

显式装箱

let s1 = 'Hello, JavaScript'
let s2 = new String(s1)
console.log(s1)                    // Hello,JavaScript
console.log(s2)                    // String {'Hello,JavaScript'}

拆箱与装箱对应,是装箱的反向操作,把引用类型数据转化为基本类型数据,通常使用valueOf()和toString()方法实现。

let s1 = new String('string')
console.log(s1, typeof(s1))        // String {'string'} 'object'
let s2 = s1.valueOf()
console.log(s2)                    // string
let s3 = s1.toString()
console.log(s3)                    // string
let n1 = new Number(123)
console.log(n1, typeof(n1))        // Number {123} 'object'
let n2 = n1.valueOf(n1)
console.log(n2)                    // 123

五、理解值类型和引用类型

上文中提到 基本数据类型是值直接存储在栈中,访问直接到栈中取

引用数据类型值存放在堆中,栈中存放着指向值的地址,访问先到栈中取地址,再根据地址到堆中找到数据

let s1 = 'str'
let s2 = s1
let n1 = 123
let o1 = {name: 'yi', age: 18}
let o2 = o1
let a1 = [1,2,3]

5.png

s2 = 'str2'
o2.name = 'yiyiyi'
console.log(s1) // str2
console.log(o1) // {name: 'yiyiyi', age: 20}

7.png 可以看出基本类型数据值直接存在栈中,修改s2不会影响s1的数据,而引用类型的数据值存在堆中,栈中存的是地址,取值时通过地址到堆中寻找,修改o2的值会影响o1

六、至少三种判断 JavaScript 数据类型的方式,以及优缺点,如何准确的判断数组类型

1、typeof

typeof 返回的都是字符串,可以返回string,boolean,number,function,object,undefined

console.log(typeof 1)                  // number
console.log(typeof 'str')              // string
console.log(typeof true)               // boolean
console.log(typeof null)               // object
console.log(typeof undefined)          // undefined
console.log(typeof function() {})      // function
console.log(typeof { name: 'yiyiyi'})  // object
console.log(typeof [1,2,3])            // object

可以看出,typeof 只能判断基础类型数据,引用类型数据包括 null 都会判断类型为 object

2、instanceof

instanceof 可以准确判断引用类型数据,判断不了基本类型数据。instanceof运算符用于测试构造函数的 prototype 属性是否出现在对象原型链中的任何位置,即instanceof右边变量的prototype在左边变量的原型链上就返回true

console.log(1 instanceof Number)                 // false
console.log(true instanceof Boolean)             // false
console.log('str' instanceof String)             // false
console.log([1,2,3] instanceof Array)            // true
console.log({} instanceof Object)                // true
console.log(function() {} instanceof Function)   // true

3、constructor

基本数据类型和引用数据类型都可以通过constructor判断,但是如果数据原型被修改,这个方法就失效

console.log((1).constructor === Number)                // true
console.log(('str').constructor === String)            // true
console.log((true).constructor === Boolean)            // true
console.log(([1,2,3]).constructor === Array)           // true
console.log(({}).constructor === Object)               // true
console.log((function() {}).constructor === Function)  // true

4、Object.prototype.toString.call()

es5文档规范定义Object.prototype.toString():Object.prototype.toString()会返回[object,[[class]]]的字符串,其中[[class]]会返回es定义的对象类型,包含"Arguments", “Array”, “Boolean”, “Date”, “Error”, “Function”,“JSON”, “Math”, “Number”, “Object”,“RegExp”,和“String”;再加上es5新增加的返回[objectUndefined]和[object Null]

console.log(Object.prototype.toString.call(1))             // [object Number]
console.log(Object.prototype.toString.call('str'))         // [object String]
console.log(Object.prototype.toString.call(true))          // [object Boolean]
console.log(Object.prototype.toString.call(null))          // [object Null]
console.log(Object.prototype.toString.call(undefined))     // [object Undefined]
console.log(Object.prototype.toString.call({}))            // [object Object]
console.log(Object.prototype.toString.call([1,2.3]))       // [object Array]
console.log(Object.prototype.toString.call(function() {})) // [object Function]

七、可能发生隐式类型转换的场景以及转换原则,应如何避免或巧妙应用

当运算符在计算时,运算符两边数据类型不同,CPU就无法计算,编译器会自动将运算符两边的数据做一个类I型转换,转为类型相同的数据在进行计算。比较运算场景下隐式类型转换比较常见,if判断也比较常见。 比如if判断的 == ,像 0、''、 null、undefined会隐式转为false,避免这种转换可以使用 === 进行强判断

八、出现小数精度丢失的原因,JavaScript可以存储的最大数字、最大安全数字,JavaScript 处理大数字的方法,避免精度丢失的方法

  • 出现小数精度丢失的原因

计算机的二进制实现和位数限制有些数无法有限表示。就像一些无理数不能有限表示,如 圆周率 3.1415926…,1.3333… 等。JS 遵循 IEEE 754 规范,采用双精度存储(double precision),占用 64 bit。

  • js可以存储的最大数字

console.log(Number.MAX_VALUE)         // 1.7976931348623157e+308
console.log(Number.MAX_SAFE_INTEGER)  // 9007199254740991

当超过±Math(2, 53) - 1就无法正确计算了

  • js处理大数字的方法

1、使用js新增的基本数据类型bigInt

console.log(11111111111111111 + 22222222222222222)                     // 33333333333333336
console.log(BigInt(11111111111111111) + BigInt(22222222222222222))     // 33333333333333336n
console.log(BigInt('11111111111111111') + BigInt('22222222222222222')) // 33333333333333333n

直接大数计算会出错,BigInt转换一下就OK了,注意BigInt转换时数字要用'',不然还是会出错

2、使用第三方库: json-bigint