JavaScript类型

2023-05-16

  • 为什么有的编程规范要求用 void 0 代替 undefined?
  • 字符串有最大长度吗?
  • 0.1 + 0.2 不是等于 0.3 么?为什么 JavaScript 里不是这样的?
  • ES6 新加入的 Symbol 是个什么东西?
  • 为什么给对象添加的方法能用在基本类型上?

基本类型

基本类型(基本数值、基本数据类型)是一种既非对象也无方法的数据。在 JavaScript 中,共有7种基本类型:string,number,bigint,boolean,null,undefined,symbol (ECMAScript 2016新增)。

  • 多数情况下,基本类型直接代表了最底层的语言实现。
  • 所有基本类型的值都是不可改变的。但需要注意的是,基本类型本身和一个赋值为基本类型的变量的区别。变量会被赋予一个新值,而原值不能像数组、对象以及函数那样被改变。
// 使用字符串方法不会改变一个字符串
var bar = "baz";
console.log(bar);               // baz
bar.toUpperCase();
console.log(bar);               // baz

// 使用数组方法可以改变一个数组
var foo = [];
console.log(foo);               // []
foo.push("plugh");
console.log(foo);               // ["plugh"]

// 赋值行为可以给基本类型一个新值,而不是改变它
bar = bar.toUpperCase();       // BAZ

基本类型值可以被替换,但不能被改变。

JavaScript是如何处理基本类型的

// 基本类型
let foo = 5;

// 定义一个貌似可以改变基本类型值的函数
function addTwo(num) {
   num += 2;
}
// 和前面的函数一样
function addTwo_v2(foo) {
   foo += 2;
}

// 调用第一个函数,并传入基本类型值作为参数
addTwo(foo);
// Getting the current Primitive value
console.log(foo);   // 5

// 尝试调用第二个函数...
addTwo_v2(foo);
console.log(foo);   // 5
  • addTwoaddTwo_v2函数调用时,JavaScript会检查标识符foo的值,从而准确无误的找到第一行实例化变量的声明语句。
  • 找到以后,JavaScript将其作为参数传递给函数的形参。
  • 在执行函数体内语句之前,JavaScript会将传递进来的参数(基本类型的值)复制一份,创建一个本地副本。这个副本只存在于该函数的作用域中,我们能够通过指定在函数中的标识符访问到它(addTwo中的numaddTwo_v2中的foo)。
  • 接下来,函数体中的语句开始执行:
    • 第一个函数中,创建了本地num参数,num的值加2,但这个值并不是原来的foo的值。
    • 第二个函数中,创建了本地参数foo,并将它的值加2,这个值不是外部foo的值。在这种情况下,外部的foo变量不能以任何方式被访问到。这是因为JavaScript的词法作用域(lexical scoping)所导致的变量覆盖,本地的变量foo覆盖了外部的变量foo。欲知详情,请参阅闭包。
  • 综上所述,函数中的任何操作都不会影响到最初的foo,我们操作的只不过是它的副本

我的理解

let foo = 5; 
function addTwo_v2(foo) {
   foo += 2;
}
addTwo(foo); 函数的调用执行过程如下
{ 
let foo = foo  //创建一个名为形参的本地变量,只不过这个形参的变量名与外部的foo变量名相同
因为JavaScript的词法作用域(lexical scoping)所导致的变量覆盖
本地的变量foo覆盖了外部的变量foo,在这种情况下,外部的foo变量不能以任何方式被访问到
且在foo = foo的过程中,将参数foo(外部变量)的值的值复制了一份给本地的foo
foo += 2 //的过程中,使用的都是内部变量foo+运算执行的过程中,是重新创建一个number基本类型,它的值为2
然后计算5+2 = 7,然后将7这个number基本类型的值重新赋值给了foo,
而不是把5这个number基本类型的值改为7
}
console.log(foo) //这时访问的是外部变量的foo,第一行的foo,所以打印的是5

也就是说在let a = 5之后,除非重新给a赋值,否则a的值一直都是5,不会改变

这就是为什么说所有基本类型的值都是无法改变的

Undefined、Null

undefined全局对象的一个属性。也就是说,它是全局作用域的一个变量。undefined的最初值就是原始数据类型undefined。

A variable that has not been assigned a value is of type undefined. A method or statement also returns undefined if the variable that is being evaluated does not have an assigned value. A function returns undefined if a value was not returned.

  • 一个还没有被赋值的变量的类型是undefined。如果一个正在被评估的方法或者声明(函数声明,变量声明)没有被赋值,那么也会返回undefined。一个函数如果没有返回值,也会返回undefined。

我的理解

  • 和Number一样,Undefined的是一种数据类型,可以理解为一种type,比如5的type是Number,而undefined的type是Undefined。也就是说Number可以有很多种值,而Undefined只有一种值。
  • 上面所说的返回undefined,返回的就是一个值。这个值也可以被赋值给别的变量。
  • 同时undefined也是全局作用域中的一个变量,这个变量的值就是undefined。类似于const undefined = undefined

为什么有的编程规范要求用 void 0 代替 undefined?

因为 JavaScript 的代码 undefined 是一个变量,而并非是一个关键字,而变量是可以被重新赋值的,所以undefined就有可能在非全局作用域中被改写。

let a = 1
{
  let undefined = a
  let b = undefined
  let c = void 0
  console.log(b)  //  1
  console.log(c)  //undefined
}
undefined = a
a = undefined
console.log(a)  // undefined

由此可以看出,在非全局作用域中undefined变量有可能被改写。void 0运算后返回的是undefined这个值,而不是变量,所以我们为了避免无意中被篡改,建议使用 void 0 来获取 undefined 值。

Null

null 特指对象的值未设置。它是 JavaScript 基本类型 之一,在布尔运算中被认为是falsy。
null 是一个字面量,不像 undefined,它不是全局对象的一个属性。null 是表示缺少的标识,指示变量未指向任何对象。把 null 作为尚未创建的对象,也许更好理解。在 API 中,null 常在返回类型应是一个对象,但没有关联的值的地方使用。

Undefined 跟 Null 有一定的表意差别,Null 表示的是:“定义了但是为空”。所以,在实际编程时,我们一般不会把变量赋值为 undefined,这样可以保证所有值为 undefined 的变量,都是从未赋值的自然状态。
Null 类型也只有一个值,就是 null,它的语义表示空值,与 undefined 不同,null 是 JavaScript 关键字,所以在任何代码中,你都可以放心用 null 关键字来获取 null 值。

我的理解

null可以理解为一个空指针指向的地方,在对象地址赋值给变量之前的变量值
let a = {}
执行后,a 的值是指向这个空对象的地址
这个过程可以理解为:

  1. let a = null 先给a赋值为null
  2. 执行new Object,返回一个object对象,放在内存中
  3. 将内存中对象的地址赋值给a

所以let a = null是已经定义了a变量,但a的值为null,也就是空。

字符串有最大长度吗?

String 用于表示文本数据。String 有最大长度是 2^53 - 1,这个所谓最大长度,并不完全是常说的字符数。
因为 String 的意义并非“字符串”,而是字符串的 UTF16 编码,我们字符串的操作 charAt、charCodeAt、length 等方法针对的都是 UTF16 编码。所以,字符串的最大长度,实际上是受字符串的编码长度影响的。

JavaScript 中的字符串是永远无法变更的,一旦字符串构造出来,无法用任何方式改变字符串的内容,所以字符串具有值类型的特征。

JavaScript 字符串把每个 UTF16 单元当作一个字符来处理。

Number

JavaScript 中的 Number 类型基本符合 IEEE 754-2008 规定的双精度浮点数规则,但是 JavaScript 为了表达几个额外的语言场景(比如不让除以 0 出错,而引入了无穷大的概念),规定了几个例外情况:

  • NaN,Not a Number;
  • Infinity,无穷大;
  • -Infinity,负无穷大。
let a = NaN
console.log(a) //NaN
let x = 0
console.log(1/x) //Infinity
console.log(-1/x) //- Infinity

值得注意的是,JavaScript 中有 +0 和 -0,在加法类运算中它们没有区别,但是除法的场合则需要特别留意区分,“忘记检测除以 -0,而得到负无穷大”的情况经常会导致错误,而区分 +0 和 -0 的方式,正是检测 1/x 是 Infinity 还是 -Infinity。

let x = +0
let y = -0
console.log(1/x) //Infinity
console.log(1/y) //-Infinity
console.log(x == y) //true
console.log(x === y) //true

NaN

NaN 是一个全局对象的属性。

NaN 属性的初始值就是 NaN,和 Number.NaN 的值一样。在现代浏览器中(ES5中), NaN 属性是一个不可配置(non-configurable),不可写(non-writable)的属性。

NaN如果通过 ==!==== 、以及 !==与其他任何值比较都将不相等 – 包括与其他 NAN值进行比较。必须使用 Number.isNaN() 或 isNaN() 函数。在执行自比较之中:NaN,也只有NaN,比较之中不等于它自己。

let a = NaN
console.log(a == NaN)   //false
console.log(a === NaN)  //false
console.log(a != NaN)  //true
console.log(a !== NaN) //true
console.log(NaN == NaN)  //false
console.log(NaN === NaN)  //false
console.log(NaN != NaN) //true
console.log(NaN !== NaN) //true

Number.NaN === NaN; // false
isNaN(NaN);         // true
isNaN(Number.NaN);  // true

Number.NaN !== NaN //true

我的理解

每个NaN都不一样,但是可以通过isNaN()函数判断它是不是NaN

  • 请注意isNaN()和Number.isNaN()之间的区别:如果当前值是NaN,或者将其强制转换为数字后将是NaN,则前者将返回true。而后者仅当值当前为NaN时才为true:
isNaN('hello world');        // true
Number.isNaN('hello world'); // false
Number.isNaN(NaN); // true

说明isNaN()会进行强制类型转换,而Number.isNaN()不会进行转换,只能判断NaN

0.1 + 0.2 不是等于 0.3 么?为什么 JavaScript 里不是这样的?

根据浮点数的定义,非整数的 Number 类型无法用 = 也不行) 来比较,浮点数运算的精度问题导致等式左右的结果并不是严格相等,而是相差了个微小的值。

0.1+0.2 == 0.3 //false
0.2+0.3 == 0.5 //true
  • 十进制小数会被转化为二进制浮点数进行计算,如果二进制表示超过52位,则会进行舍入,造成舍入误差。
  • 小数计算过程中,某些小数会因为舍入而不精确,同时计算出结果后,也有可能对结果进行舍入,从而导致两个数值结果不等。

正确的比较方法是使用 JavaScript 提供的最小精度值,绝对值小于e:

  console.log( Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON); //true

检查等式左右两边差的绝对值是否小于最小精度,才是正确的比较浮点数的方法。这段代码结果就是 true 了。

ES6 新加入的 Symbol 是个什么东西?

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

var sym1 = Symbol();
var sym2 = Symbol('foo');
var sym3 = Symbol('foo');
console.log(sym2 == sym3 ) //false

Symbol(“foo”) 不会强制将字符串 “foo” 转换成symbol类型。它每次都会创建一个新的 symbol类型。

众所周知的 symbols

除了自己创建的symbol,JavaScript还内建了一些在ECMAScript 5 之前没有暴露给开发者的symbol,它们代表了内部语言行为。

  • Symbol.iterator属性
    一个返回一个对象默认迭代器的方法。被 for…of 使用。
  • Symbol.for(key)方法
    使用给定的key搜索现有的symbol,如果找到则返回该symbol。否则将使用给定的key在全局symbol注册表中创建一个新的symbol。

Object

对象 指包含数据和用于处理数据的指令的数据结构.

为什么给对象添加的方法能用在基本类型上?

  • 在 JavaScript 中,对象的定义是“属性的集合”。属性分为数据属性和访问器属性,二者都是 key-value 结构,key 可以是字符串或者 Symbol 类型。

  • JavaScript 中的“类”仅仅是运行时对象的一个私有属性,而 JavaScript 中是无法自定义类型的

  • JavaScript 中的几个基本类型,都在对象类型中有一个内置对象
    Number;String;Boolean;Symbol。

  • 3 与 new Number(3) 是完全不同的值,它们一个是 Number 类型, 一个是对象类型。

3 == new Number(3) //true
3 === new Number(3) //false

Number、String 和 Boolean,三个构造器是两用的,当跟 new 搭配时,它们产生对象,当直接调用时,它们表示强制类型转换。

所以答案就是. 运算符提供了装箱操作,它会根据基础类型构造一个临时对象,使得我们能在基础类型上调用对应对象的方法。

类型转换

  • 很多实践中推荐禁止使用“ ==”,而要求程序员进行显式地类型转换后,用 === 比较。

StringToNumber

  • 字符串到数字的类型转换,存在一个语法结构,类型转换支持十进制、二进制、八进制和十六进制

  • JavaScript 支持的字符串语法还包括正负号科学计数法,可以使用大写或者小写的 e 来表示:1e3,-1e-2。

  • 多数情况下,Number() 是比 parseInt 和 parseFloat 更好的选择。

NumberToString

  • 在较小的范围内,数字到字符串的转换是完全符合你直觉的十进制表示。当 Number 绝对值较大或者较小时,字符串表示则是使用科学计数法表示的。

装箱转换

每一种基本类型 Number、String、Boolean、Symbol 在对象中都有对应的类,所谓装箱转换,正是把基本类型转换为对应的对象

    var symbolObject = (function(){ return this; }).call(Symbol("a"));

    console.log(typeof symbolObject); //object
    console.log(symbolObject instanceof Symbol); //true
    console.log(symbolObject.constructor == Symbol); //true

装箱机制会频繁产生临时对象,在一些对性能要求较高的场景下,我们应该尽量避免对基本类型做装箱转换。

  • 使用内置的 Object 函数,我们可以在 JavaScript 代码中显式调用装箱能力。
    var symbolObject = Object(Symbol("a"));

    console.log(typeof symbolObject); //object
    console.log(symbolObject instanceof Symbol); //true
    console.log(symbolObject.constructor == Symbol); //true

拆箱转换

  • 在 JavaScript 标准中,规定了 ToPrimitive 函数,它是对象类型到基本类型的转换(即,拆箱转换)。对象到 String 和 Number 的转换都遵循“先拆箱再转换”的规则。
  • 通过拆箱转换,把对象变成基本类型,再从基本类型转换为对应的 String 或者 Number。
  • 拆箱转换会尝试调用 valueOf 和 toString 来获得拆箱后的基本类型。如果 valueOf 和 toString 都不存在,或者没有返回基本类型,则会产生类型错误 TypeError。

    var o = {
        valueOf : () => {console.log("valueOf"); return {}},
        toString : () => {console.log("toString"); return {}}
    }

    o * 2
    // valueOf
    // toString
    // TypeError

    String(o)
    // toString
    // valueOf 
    // TypeError

number拆箱,先执行valueOf,再执行toString
string拆箱,先执行toString,再执行valueOf

类型对照

image

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JavaScript类型 的相关文章

  • 关闭选项卡时要求确认[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 当我在某些浏览器上关闭页面时 我希望出现一个消息框 并询问我是否真的要关闭页面 有两个按钮 如果我单击No那么这个标签就不会被关闭 我怎样
  • React js Stripe 结账不起作用

    我正在尝试在 React js 应用程序中呈现条带结账默认表单
  • 不和谐机器人 |不和谐.js |类型错误:无法读取未定义的属性“长度”

    我正在制作一个 Discord 机器人 并且正在使用 CodeLyon 的视频作为参考 该错误位于我的 message js 文件中 该文件包含以下内容 require dotenv config create cooldowns map
  • 为什么 JavaScript base-36 转换看起来不明确

    我目前正在编写一段使用 Base 36 编码的 JavaScript 我遇到了这个问题 parseInt welcomeback 36 toString 36 看来要回归了 welcomebacg 我在 Chrome 开发者控制台和 Nod
  • 如何重定向到 instagram://user?username={username}

    我的 html 页面上有这个链接 可以在特定用户上打开 Instagram 应用程序 a href Link to Instagram Profile a 我一直在寻找自动运行 url instagram user username USE
  • 如何重置使用 JavaScript 更改的 CSS 属性?

    我的导航按钮的宽度从 100px 增加到 150px 当鼠标悬停在 nav li hover width 150px 但是使用 javascript 我已经做到了 无论选择哪个选项 宽度都将继续为 150px 当选择每个选项时 它会使其他选
  • 使用 useReducers 调度函数发送多个操作?

    使用时是否可以通过调度函数发送多个动作useReducer挂钩反应 我尝试向它传递一组操作 但这会引发未处理的运行时异常 明确地说 通常会有一个初始状态对象和一个减速器 如下所示 const initialState message1 nu
  • 我想检查 $('#td1').text() === "x" 是否?

    我想检查innerHtml是否有X或O 所以我不能再次添加任何其他东西 但它不起作用 添加检查代码后它就停止了 我在这里尝试做一个简单的XO游戏来更熟悉javascript和jquery 我也不确定是否可以用 jQuery 做到这一点
  • 使用 jQuery/JS 打开时使
    标签的内容具有动画效果

    我只想要 HTML5 的内容details标记为 滑行 动画打开 而不是仅仅弹出打开 立即出现 这可以用 jQuery Javascript 实现吗 Fiddle http jsfiddle net 9h4Hq HTML
  • 检查 JavaScript 字符串是否为 URL

    JavaScript 有没有办法检查字符串是否是 URL 正则表达式被排除在外 因为 URL 很可能是这样写的stackoverflow 也就是说它可能没有 com www or http 如果你想检查一个字符串是否是有效的 HTTP UR
  • 除了更改标题之外,如何在 Firefox 中强制另存为对话框?

    有没有办法在 ff 中强制打开 www example com example pdf 的另存为对话框 我无法更改标题 如果您可以将文件以 Base64 格式输出到客户端 则可以使用 data uri 进行下载 location href
  • 为什么是 javascript:history.go(-1);无法在移动设备上工作?

    首先 一些背景 我有一个向用户呈现搜索页面 html 表单 的应用程序 填写标准并单击 搜索 按钮后 结果将显示在标准部分下方 在结果列表中 您可以通过单击将您带到新页面的链接来查看单个结果的详细信息 在详细信息页面中 我添加了一个 返回结
  • Jquery/Javascript 上传和下载文件,无需后端

    是否可以在没有后端服务器的情况下在 JavaScript 函数中下载和上传文件 我需要导出和导入由 JavaScript 函数生成的 XML 我想创建按钮 保存 xml 来保存文件 但我不知道是否可行 另一方面 我希望将 XML 文件直接上
  • 跟踪用户何时点击浏览器上的后退按钮

    是否可以检测用户何时单击浏览器的后退按钮 我有一个 Ajax 应用程序 如果我可以检测到用户何时单击后退按钮 我可以显示适当的数据 任何使用 PHP JavaScript 的解决方案都是优选的 任何语言的解决方案都可以 只需要我可以翻译成
  • FireFox 中的自动滚动

    我的应用程序是实时聊天 我有一个 Div 来包装消息 每条消息都是一个 div 所以 在几条消息之后 我的 DOM 看起来像这样 div div Message number two div div div div
  • 有没有办法阻止 prettier / prettier-now 将函数参数分解为新行

    当使用 prettier prettier now 在保存时进行格式化时 当一个函数包装另一个函数时 它会中断到一个新行 我想知道是否有办法阻止这种行为 例如 期望的输出 app get campgrounds id catchAsync
  • Javascript 纪元时间(以天为单位)

    我需要以天为单位的纪元时间 迄今为止 我已经看到过有关如何翻译它的帖子 但几天后就没有了 我对纪元时间很不好 我怎么能得到这个 我需要以天为单位的纪元时间 我将解释为您想要自纪元以来的天数 纪元本身是第 0 天 或第 1 天的开始 无论您如
  • 如何获取浏览器视口中当前显示的内容

    如何获取当前正在显示长文档的哪一部分的指示 例如 如果我的 html 包含 1 000 行 1 2 3 9991000 并且用户位于显示第 500 行的中间附近 那么我想得到 500 n501 n502 或类似的内容 显然 大多数场景都会比
  • 导致回发到与弹出窗口不同的页面

    我有一个主页和一个详细信息页面 详细信息页面是从主页调用的 JavaScript 弹出窗口 当单击详细信息页面上的 保存 按钮时 我希望主页 刷新 是否有一种方法可以调用主页的回发 同时还可以从详细信息页面维护保存回发 Edit 使用win
  • fullCalendar 未显示正确的结束日期

    我正在看调试页面 http jsbin com wukofacaxu edit js outputFullCalendar 官方网站的 我想安排一个活动时间为 22 09 2015 至 30 09 2015 dd mm yyyy 但它只显示

随机推荐