乐趣区

关于前端:深入理解JavaScriptJavaScript-由什么组成

这是一个根底构造问题,一个残缺的 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 中则以小写的模式展现展现数据类型,笔者这里以网站为更大根据

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

根本类型贮存在栈内存中

援用类型贮存在堆内存中

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

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

// 案例 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—— 对象

系列文章

  • 深刻了解 JavaScript- 开篇
  • 深刻了解 JavaScript-JavaScript 是什么
退出移动版