一语中的
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 操作符可以确定值的「原始类型」,也就是说,该操作只能区分基本数据类型,而对于复杂数据类型就鞭长莫及了。
代码语言:javascript复制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 "]" 形式的字符串。
针对基本数据类型,通过装箱过程转为对象类型。
代码语言:javascript复制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可以将数据类型很容易的分开。但是,每次进行判断的时候,多了一堆额外的信息。所以,我们可以对该方法进行改进。
代码语言:javascript复制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 的原型」上,因此默认在所有函数和类上都可以调用。
代码语言:javascript复制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]]
指针就会被赋值为构造函数的原型对象。
❝实例与构造函数原型之间有直接的联系,但实例与构造函数之间没有 ❞
所以,可以通过实例和构造函数原型的关系,来判断是否实例类型。
代码语言:javascript复制''.constructor === String; // true;
true.constructor === Boolean; // true;
new Number(1).constructor === Number // number 类型存在包装对象
❝
null
/undefined
是一个「假值」,没有对应包装对象(无法进行装箱操作),也不是任何构造函数的实例。所以,不存在原型,即,无法使用constructor判断类型。 ❞
类型转换(装箱/拆箱)
「基本类型是没有任何属性和方法」
此时,有人就会有一个疑问,当定义了let str = '789';
,此时可以通过str
进行属性和方法调用。这不是和上面的那个相悖嘛。
其实,针对基本类型的属性和方法的调用,都是在基本类型的包装对象上进行操作。
装箱转换
每一种基本类型 Number、String、Boolean、Symbol 在对象中都有对应的类,所谓装箱转换,正是把基本类型转换为对应的对象,它是类型转换中一种相当重要的种类。
代码语言:javascript复制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