人的不幸来源于他不肯安分守己地待在自己应该待的房里里 --《香水》
一语中的
JS = ECMAScript + DOM + BOM
- DOM 并非只能通过 JS 访问
- JS是动态弱类型语言
- 每个变量只不过是一个用于保存任意值的命名占位符
- 实例与构造函数原型之间有直接的联系,但实例与构造函数之间没有
- 基本类型是没有任何属性和方法
- 对象其实就是一组数据和功能的集合
文章概要
- JS组成
- JS数据类型(7+1)
- 类型转换(装箱/拆箱)
JS组成
其实这是一个很容易忽略的问题。俗话说,最熟悉的陌生人说的就是这种情况。
在浏览器环境下,JS = ECMAScript + DOM + BOM
。
我们来简单介绍下。(后期会单独针对BOM/DOM进行分析)
ECMAScript
JS的核心部分,即 ECMA-262 定义的语言,并不局限于 Web 浏览器。
Web 浏览器只是 ECMAScript 实现可能存在的一种*宿主环境(host environment)。而宿主环境提供ECMAScript 的基准实现和与环境自身交互必需的扩展。(比如 DOM 使用 ECMAScript 核心类型和语法,提供特定于环境的额外功能)。
像我们比较常见的Web 浏览器、 Node.js和即将被淘汰的 Adobe Flash都是ECMA的宿主环境。
ECMAScript 只是对实现ECMA-262规范的一门语言的称呼, JS 实现了ECMAScript,Adobe ActionScript 也实现ECMAScript。
文档对象模型(DOM)
DOM是一个应用编程接口(API),通过创建表示文档的树,以一种独立于平台和语言的方式访问和修改一个页面的内容和结构。
在HTML文档中,Web开发者可以使用JS来CRUD DOM 结构,其主要的目的是动态改变HTML文档的结构。
DOM 将整个页面抽象为一组分层节点
DOM 并非只能通过 JS 访问, 像可伸缩矢量图(SVG)、数学标记语言(MathML)和同步多媒体集成语言(SMIL)都增加了该语言独有的 DOM 方法和接口。
浏览器对象模型(BOM)
用于支持访问和操作浏览器的窗口。
针对浏览器窗口和子窗口(frame)提供了
- 弹出新浏览器窗口的能力
- 移动、缩放和关闭浏览器窗口的能力
- navigator 对象,提供关于浏览器的详尽信息
- location 对象,提供浏览器加载页面的详尽信息
- screen 对象,提供关于用户屏幕分辨率的详尽信息
- performance 对象,提供浏览器内存占用、导航行为和时间统计的详尽信息
- 对 cookie 的支持
- 其他自定义对象,如 XMLHttpRequest 和 IE 的 ActiveXObject
JS数据类型
每种编程语言都具有内建的数据类型,而根据使用数据的方式从两个不同的维度将语言进行分类。
- 动态/静态:
- 动态类型:运行过程中需要检查数据类型
- 静态类型:使用之前就需要确认其变量数据类型
- 强/弱:
- 强类型:不支持隐式类型转换
- 弱类型:支持隐式类型转换
隐式类型转换 :在赋值过程中,编译器会把 int 型的变量转换为 bool 型的变量
通过上述的介绍和平时大家的使用JS的数据类型发现。
JS是动态弱类型语言。
由于JS的语言特性,我们可以进而得出另外一个结论:每个变量只不过是一个用于保存任意值的命名占位符。
而谈到JS数据类型,就绕不开针对数据的分类。你没猜错,还是一样的配方,大家熟悉的味道。
ECMAScript 有8 种数据类型
- Undefined
- Null
- Boolean
- String
- Number
- Symbol (ES6新增)
- BigInt (ES2020新增)
- Object (基本引用类型、)
根据数据存储位置的不同,我们将JS数据类型分为两大类:
- 基本数据类型(primary) 存放在栈内存中,类型1-7
- 复杂数据类型/引用类型 存放在堆内存中, 类型8
针对老生常谈的问题,我们来搞点不一样的。
JS 判断数据类型方式(4种)
该问题在一些面试题中,出现的频率还挺高。(敲黑板,考试要考!)
1. typeof
typeof 操作符可以确定值的原始类型,也就是说,该操作只能区分基本数据类型,而对于复杂数据类型就鞭长莫及了。
let un, nu =null, bo = true, st = '789', num = 789,sy = Symbol('789'),bi = 789n; typeof un; // "undefined" typeof nu; // "object" 这是一个特例,或者说null就是一个特例 typeof bo; //"boolean" typeof st; // "string" typeof num;// "number" typeof sy; // "symbol" typeof bi; // "bigint" 复制代码
特例分析: null值表示一个空对象指针。所以针对
typeof null
返回了一个"object"。
2. Object.prototype.toString.call(xx)
若参数(xx)不为 null 或 undefined,则将参数转为对象,再作判断。转为对象后,取得该对象的 [Symbol.toStringTag] 属性值(可能会遍历原型链)作为 tag,然后返回 "[object " + tag + "]" 形式的字符串。
针对基本数据类型,通过装箱过程转为对象类型。
Object.prototype.toString.call(null) //[ojbect Null] Object.prototype.toString.call(undefined) //[object Undefined] Object.prototype.toString.call(true) // [object Boolean] Object.prototype.toString.call(()=>{}) // [object Function] 复制代码
通过Object.prototype.toString可以将数据类型很容易的分开。但是,每次进行判断的时候,多了一堆额外的信息。所以,我们可以对该方法进行改进。
function getDataType(type){ return Object.prototype.toString.call(type) .split(' ')[1] .slice(0,-1) .toLocaleLowerCase(); } getDataType(null) //null getDataType(undefined) // undefined getDataType(true) // boolean getDataType(()=>{}) // function 复制代码
3. instanceof
在一些资料中讲到,instanceof 是用来判断 a 是否为 B 的实例,表达式为:a instanceof B,如果 a 是 B 的实例,则返回 true,否则返回 false。
其实这句话是不严谨的。准确的描述应该是:a instanceof B
用于判断实例a
的原型链中出现过相应的构造函数B
,则 instanceof 返回 true 。 instanceof 判断的是 a和B是否有血缘关系,而不是仅仅根据是否是父子关系。
let ar = []; ar instanceof Array // true ar instanceof Object // true 如果按照实例的关系的话,这应该返回false 复制代码
多说一句,在ES6中instanceof
操作符会使用 Symbol.hasInstance
函数来确定关系。
这个属性定义在 Function 的原型上,因此默认在所有函数和类上都可以调用。
function Car() {} let c = new Car(); console.log(Car[Symbol.hasInstance](c)); // true 复制代码
我们可以通过重新定义该方法,来改变instanceof
的值。
class Parent{} class Child extends Parent { static [Symbol.hasInstance]() { return false; } } let c = new Child(); console.log(Parent[Symbol.hasInstance](c)); // true console.log(c instanceof Parent); // true // console.log(Child[Symbol.hasInstance](c)); // false console.log(c instanceof Child); // false 复制代码
(这里再埋一个伏笔,后期会有针对原型的文章)
4. constructor
只要创建一个函数,就会按照特定的规则为这个函数创建一个 prototype 属性(指向原型对象)。默认情况下,所有原型对象自动获得一个名为 constructor 的属性,指回与之关联的构造函数。
每次调用构造函数创建一个新实例,实例的内部[[Prototype]]
指针就会被赋值为构造函数的原型对象。
实例与构造函数原型之间有直接的联系,但实例与构造函数之间没有
所以,可以通过实例和构造函数原型的关系,来判断是否实例类型。
''.constructor === String; // true; true.constructor === Boolean; // true; new Number(1).constructor === Number // number 类型存在包装对象 复制代码
null
/undefined
是一个假值,没有对应包装对象(无法进行装箱操作),也不是任何构造函数的实例。所以,不存在原型,即,无法使用constructor判断类型。
类型转换(装箱/拆箱)
基本类型是没有任何属性和方法
此时,有人就会有一个疑问,当定义了let str = '789';
,此时可以通过str
进行属性和方法调用。这不是和上面的那个相悖嘛。
其实,针对基本类型的属性和方法的调用,都是在基本类型的包装对象上进行操作。
装箱转换
每一种基本类型 Number、String、Boolean、Symbol 在对象中都有对应的类,所谓装箱转换,正是把基本类型转换为对应的对象,它是类型转换中一种相当重要的种类。
let str = '789'; str.length; //3 属性调用 str.slice(1); // "89" 方法调用 =======等价于 let strObj = new String(789); strObj.length; //3 strObj.slice(1); //"89" 复制代码
拆箱转换
在 JavaScript 标准中,规定了 ToPrimitive
函数,它是对象类型到基本类型的转换(即,拆箱转换)。
对象到 String 和 Number 的转换都遵循“先拆箱再转换”的规则。通过拆箱转换,把对象变成基本类型,再从基本类型转换为对应的 String 或者 Number。
拆箱转换会尝试调用 valueOf 和 toString 来获得拆箱后的基本类型。如果 valueOf 和 toString 都不存在,或者没有返回基本类型,则会产生类型错误 TypeError
。
let o = { valueOf : () => {console.log("valueOf"); return {}}, toString : () => {console.log("toString"); return {}} } o * 2 // valueOf // toString // TypeError 复制代码
对象的Symbol.toPrimitive
属性,指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。
let o = { valueOf : () => {console.log("valueOf"); return {}}, toString : () => {console.log("toString"); return {}} } o[Symbol.toPrimitive] = () => {console.log("toPrimitive"); return "hello"} console.log(o + "") // toPrimitive // hello 复制代码