这是一个根底构造问题,一个残缺的 JavaScript 包含:ECMAScript 、文档对象模型(DOM)、浏览器对象模型(BOM),本章解说 ECMAScript

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

  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 教程、MDN中则以小写的模式展现展现数据类型,笔者这里以网站为更大根据

根本类型和援用类型的区别

根本类型贮存在栈内存中

援用类型贮存在堆内存中

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

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

// 案例1var string1 = 'foo';var string2 = string1;string1 = 'bar';console.log(string1, string2); // 输入 bar, foo// 案例2var 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); // undefinedconsole.log(typeof string1); // stringconsole.log(typeof number1); // numberconsole.log(typeof boolean1); // booleanconsole.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); // 输入 objectconsole.log(typeof myNumber); // 输入 objectconsole.log(typeof myBoolean); // 输入 objectconsole.log(typeof myObject); // 输入 objectconsole.log(typeof myArray); // 输入 objectconsole.log(typeof myFunction); // 输入 function 须要留神console.log(typeof myDate); // 输入 objectconsole.log(typeof myRegExp); // 输入 objectconsole.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

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

名称能检测不能检测
typeofstring、number、boolean、undefined 以及 functionnull 以及除 function 外的对象,后果都为 object
instanceof精确地判断简单援用数据类型不能正确判断根底数据类型
constructorstring、number、boolean、array、object、function 以及 构造函数undefined、null。不平安,因为指向能够扭转
Object.prototype.toString()内置(原生)构造函数自定义构造函数

咱们理解了根本类型、援用类型,它们的区别以及如何辨别它们,但根本类型是简略的,而援用类型尽管只有 object,但它却在 JavaScript 占了大部分知识点,只有把握 object,才算学会 JavaScript

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

系列文章

  • 深刻了解JavaScript-开篇
  • 深刻了解JavaScript-JavaScript 是什么