深入理解JavaScript-JavaScript 由什么组成

简介: 深入理解JavaScript-JavaScript 由什么组成

这是一个基础结构问题,一个完整的 JavaScript 包括:ECMAScript 、文档对象模型(DOM)、浏览器对象模型(BOM),本章讲解 ECMAScript

ECMAScript[1] 规定这门语言由以下组成:


  1. 语法
  2. 变量和数据类型
  3. 关键字和保留字
  4. 操作符
  5. 语句
  6. 对象


也就是说,这六大模块组成了这门语言

这六大支柱中,像「语法」、「变量」和「数据类型」、「关键字」和「保留字」、「操作符」、「语句」都是极好理解的,只「对象」有点复杂,下节会对其进行说明,这里我们来看看「数据类型」


数据类型


JavaScript 的数据类型分为「基本类型」和 「引用类型」


基本类型在不同的书(教程中)叫法不同,它同样被叫做基本类型/值类型/原始值/原始类型。当然引用类型也有不一样的叫法,如对象类型/复杂类型


基本类型包含 undefined 、null、string、number、boolean、symbol(ES6 新增)、bigint(ES10新增)


引用类型则是object(一组属性的集合)


特此说明:

在《JavaScript 高级程序设计第四版》中曾把数据类型归纳为驼峰形式的Undefined、Null、Boolean、Number、String 、 Symbol、BigInt、Object,而在网站现代 JavaScript 教程[2]MDN[3]中则以小写的形式展示展示数据类型,笔者这里以网站为更大依据


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


基本类型储存在栈内存中

引用类型储存在堆内存中

基本类型花销的内存小,所以拷贝值

引用类型花销的内存大,所以拷贝引用地址

image.png


// 案例1
var string1 = 'foo';
var string2 = string1;
string1 = 'bar';
console.log(string1, string2); // 输出 bar, foo
// 案例2
var object1 = {
    name: 'johan',
    age: 23,
};
var object2 = object1;
object1.name = 'elaine';
object2.age = 22;
console.log(object1, object2);
// 输出 { name: 'elaine', age: 23 } { name: 'elaine', age: 23 }


因为基本类型是存放在栈内存中,string1 赋值给 string2 后,相当于开了一个独立空间给 string2,string1 和 string2 是完全独立的两个个体,再给 string1 赋值什么,都与 string2 无关


而引用类型之所以叫引用类型(或叫复杂类型/复杂值),因为对象的地址是引用的,Object 可以一直嵌套,Function 也可以嵌套很多很多层(回调函数),Array 可以变花样的做出三维数组之类,总之,对象值可以无限大。值越大,就越占内存,如果一个很大的值随意复制,那对使用者来说就是灾难(不知不觉中就占了很大的内存)。所以它不可能随意复制,而是用指针的方式来将对象指向同一个地址(这里可以衍生到深拷贝)


当你创建一个 object1,就在堆内存中开辟一个内存。如果你赋值给 object2,其实就是把 object1 指向堆内存的地址复制给 object2,它们指向的是同一个地址。再在 object1 或者 object2 做修改的话,相当于在同一内存上做修改,无论 object1 修改还是 object2 修改都会改变


var obj1 = {};
var obj2 = obj1;
obj1.name = 'elaine';
obj2.age = 23;
console.log(obj1); // { name: 'elaine', age: 22};
console.log(obj2); // { name: 'elaine', age: 22};


衍生思考:虽然说 JavaScript 的动态性很是方便,但是如果一些新手修改了对象类型的值而不告知,那么就成了灾难。因为动态性+全局作用域,所有变量命名就成了问题,所以一般的库都是用IIFE、闭包等方法破局,再后面就有了模块化(ES module)的概念,其本质是为了解决变量命名、语言动态性的问题


光知道 JavaScript 的数据类型不够,如何更准确的知道每个数据类型

判断数据类型的方式有四种

  • 使用 typeof
  • 使用 instanceof
  • 使用 constructor
  • 使用 Object.prototype.toString.call()


类型判断


typeof 操作符


作用:返回正在使用的值的基本类型


// 基本类型/值类型/原始值/原始类型 
var null1 = null;
var undefined1 = undefined;
var string1 = 'foo';
var number1 = Number('10');
var boolean1 = Boolean('true');
var symbol1 = Symbol('foo');
console.log(typeof null1); // object, 需要注意
console.log(typeof undefined1); // undefined
console.log(typeof string1); // string
console.log(typeof number1); // number
console.log(typeof boolean1); // boolean
console.log(typeof symbol1); // symbol
// 引用类型/复杂值
var myString = new String('male');
var myNumber = new Number(23);
var myBoolean = new Boolean(false);
var myObject = new Object();
var myArray = new Array('foo', 'bar');
var myFunction = new Function('x', 'y', 'return x * y');
var myDate = new Date();
var myRegExp = new RegExp('\\bt[a-z]+\\b');
var myError = new Error('error');
console.log(typeof myString); // 输出 object
console.log(typeof myNumber); // 输出 object
console.log(typeof myBoolean); // 输出 object
console.log(typeof myObject); // 输出 object
console.log(typeof myArray); // 输出 object
console.log(typeof myFunction); // 输出 function 需要注意
console.log(typeof myDate); // 输出 object
console.log(typeof myRegExp); // 输出 object
console.log(typeof myError); // 输出 object


提示:typeof xxx 和 typeof(xxx) 一个意思


通过 typeof 操作符能判断出使用的值的类型。需要注意判断引用类型时的问题

  1. null 类型会返回 object
  2. new Function,返回的是 function


随便一说,这也是我们看很多源码或者自己写代码时常用 typeof 来判断 function 类型

if (typeof XXX === 'function') {
    // 如果XXX是function的话做什么操作
}


PS:《JavaScript 启示录》里第一章第八节里说 RegExp() 的类型返回的是 function,但是笔者检验后发现并不是,笔者猜测因为老版本的浏览器对 RegExp 的判断为 funtion,而笔者用 Chrome 浏览器表示 RegExp 的类型为 object


instanceof 运算符


instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上


function People(name, age) {
    this.name = name;
    this.age = age;
}
const elaine = new People('elaine', 23);
console.log(elaine instanceof People);
console.log(elaine instanceof Object);


instanceof 运算符能让我们找到它的爸爸是谁(谁制造了它,从生理上讲应该是妈妈是谁),以及它的祖宗十八代,这也是面试中常遇到的——instanceof 的原理是什么


constructor 构造方法


constructor 是一种用于创建和初始化 class 创建的对象的特殊方法。请注意,它是函数(方法)


就好比:

function sayHello() {
    console.log('hello');
}

不要因为单词陌生误以为它是属性


语法:

constructor([arguments]) { ... }


这里多说一句,React 类组件是这样写的:

class HelloWorld extends React.Component {
    constructor(props) {
        super(props);
    }
}


这里的意思很明白,HelloWorld 组件继承了 React.Component 组件,constructor(props) 意味着调用了父类的构造函数,并将 props 传递给 HelloWorld,使用 super 是因为在派生类中,必须调用 super 才能使用 this,当然,这部分就关系到 class 的知识,在此不表


关于构造函数(constructor)需要知道的是它是 Object 原型上的方法,即 Object.prototype.constructor,所有对象都会有 constructor 属性,它指向该对象的构造函数,关于继承后续文章会讲解


说完题外话,我们继续来看 constructor 能否检验数据类型吗


// 借 typeof 中的例子,直接打印看效果
// 基本类型
console.log(null1.constructor); // Cannot read properties of null (reading 'constructor')
console.log(undefined1.constructor); // Cannot read properties of null (reading 'constructor')
console.log(string1.constructor); // String() { [native code] }
console.log(number1.constructor); // Number() { [native code] }
console.log(boolean1.constructor); // Boolean() { [native code] }
console.log(symbol1.constructor); // Symbol() { [native code] }
// 引用类型
console.log(myString.constructor); // String() { [native code] }
console.log(myNumber.constructor); // Number() { [native code] }
console.log(myBoolean.constructor); // Boolean() { [native code] }
console.log(myObject.constructor); // Object() { [native code] }
console.log(myArray.constructor); // Array() { [native code] }
console.log(myFunction.constructor); // Function() { [native code] }
console.log(myDate.constructor); // Date() { [native code] }
console.log(myRegExp.constructor); // RegExp() { [native code] }
console.log(myError.constructor); // Error() { [native code] }


综上所述,constructor 对 underfined 和 null 无效(因为它们不是对象,不能从 Object.prototype 上继承 constructor 属性)


此外, constructor 的指针是可以改变的(因为它就是个属性,以下例子属于属性赋值)

function Person() {}
function Student() {}
Student.prototype = new Person();
var student = new Student();
console.log(student.constructor); // Person() {}


具体我们在原型一文中做详细介绍


Object.prototype.toString.call(source)


toString() 方法返回一个表示该对象的字符串

每个对象都有一个toString() 方法(继承自 Object.prototype 老祖),用它能真正做到对类型的检测


// 继续引用上述例子
// 基本类型
console.log(Object.prototype.toString.call(null1)); //[object Null]
console.log(Object.prototype.toString.call(undefined1)); //[object Undefined]
console.log(Object.prototype.toString.call(string1)); //[object String]
console.log(Object.prototype.toString.call(number1)); //[object Number]
console.log(Object.prototype.toString.call(boolean1)); //[object Boolean]
console.log(Object.prototype.toString.call(symbol1)); //[object Symbol]
// 引用类型
console.log(Object.prototype.toString.call(myString)); //[object String]
console.log(Object.prototype.toString.call(myNumber)); //[object Number]
console.log(Object.prototype.toString.call(myBoolean)); //[object Boolean]
console.log(Object.prototype.toString.call(myObject)); //[object Object]
console.log(Object.prototype.toString.call(myArray)); //[object Array]
console.log(Object.prototype.toString.call(myFunction)); //[object Function]
console.log(Object.prototype.toString.call(myDate)); //[object Date]
console.log(Object.prototype.toString.call(myRegExp)); //[object RegExp]
console.log(Object.prototype.toString.call(myError)); //[object Error]


我们可以看到它返回 [object NativeConstructorName] 格式的字符串,它能清晰的判断我们所需要的原生构造函数。同样,它的缺点是不能检测非原生构造函数


在 jquery 中的$.type() 是内部的原理用的就是 Object.prototype.toString.call(),让我们手写一个类型判断的小小工具库吧


function isType(source) {
    const target = Object.prototype.toString.call(source);
    switch(target) {
        case "[object Null]":
            return 'null';
        case "[object Undefined]":
            return 'undefined';
        case "[object String]":
            return 'string';
        case "[object Number]":
            return 'number';
        case "[object Boolean]":
            return 'boolean';
        case "[object Object]":
            return 'object';
        case "[object Array]":
            return 'array';
        case "[object Function]":
            return 'function';
        case "[object Date]":
            return 'date';
        case "[object RegExp]";
            return 'regexp';
        case "[object Error]";
            return 'error'
    }
}
function getType(target) {
    return Object.prototype.toString.call(target);
}


这里你或许会感到疑惑,Object.prototype 的 constructor 不能调用 null、undefined,而Object.prototype.toString 却能调用。这是为什么?


这里牵扯到隐式原型继承,即用对象字面量时,就已经实现了继承,而Object.prototype.toString 用的是原始方法,不信,你看


console.log(null1.toString()); // Cannot read properties of null (reading 'toString')
console.log(undefined1.toString()); // Cannot read properties of undefined (reading 'toString')


总结


这一节我们讲了 JavaSript 由什么组成

从组成上分,它包括语法,变量和数据类型,关键字和保留字,操作符,语句,对象

其中数据类型包括基本类型(简单类型/值类型)和引用类型(复杂类型)。基本类型有 number、string、boolean、null、undefined 、symbol、bigInt,引用类型则是 object

那如何判断数据类型呢?笔者总结四种方法


名称 能检测 不能检测
typeof string、number、boolean、undefined 以及 function null 以及除 function 外的对象,结果都为 object
instanceof 准确地判断复杂引用数据类型 不能正确判断基础数据类型
constructor string、number、boolean、array、object、function 以及 构造函数 undefined、null。不安全,因为指向可以改变
Object.prototype.toString() 内置(原生)构造函数 自定义构造函数


我们了解了基本类型、引用类型,它们的区别以及如何区分它们,但基本类型是简单的,而引用类型虽然只有 object,但它却在 JavaScript 占了大部分知识点,只有掌握 object,才算学会 JavaScript

下一篇,我们讲讲 JavaScript 中的 KING—— 对象


参考资料


[1] ECMAScript: https://zh.wikipedia.org/wiki/ECMAScript

[2] 现代 JavaScript 教程: https://zh.javascript.info/types

[3] MDN: https://developer.mozilla.org/zh-CN/docs/Glossary/Primitive

相关文章
|
Java
Java 实现模拟斗地主游戏
欢迎阅读本篇博客,在这篇博客中,我们将详细讲解如何使用Java编写一个简单的模拟斗地主游戏。这个项目将帮助您了解Java编程中的一些基本概念,如面向对象编程、集合框架的使用、随机数生成等。
303 0
|
机器学习/深度学习 人工智能 自然语言处理
CodeWave智能开发平台--试用--目标:AI应用广场试用1
CodeWave智能开发平台--试用--目标:AI应用广场试用1
|
Arthas 测试技术
Arthas调试案例:Trace案例
Arthas调试案例:Trace案例
|
11月前
|
算法
Leetcode第45题(跳跃游戏II)
这篇博客文章讨论了如何使用贪心算法解决LeetCode第45题“跳跃游戏II”,目的是找到使用最少跳跃次数到达数组末尾的策略。
223 8
Leetcode第45题(跳跃游戏II)
|
JavaScript
Vue3 : ref 与 reactive
Vue3 : ref 与 reactive
290 1
|
消息中间件 JSON Kubernetes
在k8S中,Fluentd的工作原理是什么?
在k8S中,Fluentd的工作原理是什么?
|
人工智能 C++
【Azure Developer】上手 The Best AI Code "Cursor" : 仅仅7次对话,制作个人页面原型,效果让人惊叹!
本文介绍了使用 Cursor 这款 AI 辅助编程工具的步骤与体验。通过下载安装 Cursor 并使用 GitHub 账号登录,你可以创建 HTML 文件并借助 AI 自动生成代码。文章详细描述了如何逐步优化生成的内容,包括调整布局、增加样式及响应式设计等。此外,还展示了通过多次迭代改进后的最终效果,并提供了生成的 HTML 模板代码,便于读者直接使用或进一步修改。
435 3
|
安全 Windows
Windows提权/杀软进程在线对比
Windows提权/杀软进程在线对比
611 0
Windows提权/杀软进程在线对比
|
C# 开发者 Windows
全面指南:WPF无障碍设计从入门到精通——让每一个用户都能无障碍地享受你的应用,从自动化属性到焦点导航的最佳实践
【8月更文挑战第31天】为了确保Windows Presentation Foundation (WPF) 应用程序对所有用户都具备无障碍性,开发者需关注无障碍设计原则。这不仅是法律要求,更是社会责任,旨在让技术更人性化,惠及包括视障、听障及行动受限等用户群体。
324 0
|
消息中间件 负载均衡 Java
【Kafka】Kafka 中消费者与消费者组的关系与负载均衡实现
【4月更文挑战第11天】【Kafka】Kafka 中消费者与消费者组的关系与负载均衡实现