JS的数据类型有哪些?
JS的数据类型可分为8种2大类。
基本数据类型:number、string、boolean、null、undefined、symbol、bigint
引用数据类型:object(包含array、function、data、RegExp等)
null与undefined的区别
null和undefined基本是同义的,只有一些细微的差别。
- null表示“没有对象”,即该处不应该有值。典型用法是:
(1)作为函数的参数,表示该函数的参数不是对象
(2)作为对象原型链的终点
Object.getPrototypeOf(Object.prototype) //null
- undefined表示“缺少值”,就是此处应该有一个值,但是还没有定义。典型用法是:
(1)变量被声明了,但没有赋值时,就等于undefined
(2)调用函数时,应该提供的参数没有提供,该参数等于undefined
(3)对象没有赋值的属性,该属性的值为undefined
(4)函数没有返回返回值时,默认返回undefined
var i;
i //undefined
var o = new Object();
o.p //undefined
如何判断数据类型
判断方式有:typeof、instanceof、Object.prototype.toString.call()、constructor
typeof
适用于基本类型和函数判断,不适合用于Object类型的进一步判断。
const obj = { name: 'zhangsan', age: 18 }
console.log(typeof obj) //object
const array = [1,2,3]
console.log(typeof array) //object
const string = 'this is a string'
console.log(typeof string) //string
const boolean = true
console.log(typeof boolean) //boolean
const number = 1
console.log(typeof number) //number
const test_null = null
console.log(typeof test_null) //object
const test_undefined = undefined
console.log(typeof test_undefined) //undefined
const func = () => {console.log(2)}
console.log(typeof func) //function
instanceof
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。 instanceof 不适用于判断基本类型。
// 定义构造函数
function C(){}
function D(){}
var o = new C();
o instanceof C; // true,因为 Object.getPrototypeOf(o) === C.prototype
o instanceof D; // false,因为 D.prototype 不在 o 的原型链上
o instanceof Object; // true,因为 Object.prototype.isPrototypeOf(o) 返回 true
C.prototype instanceof Object // true,同上
Object.prototype.toString.call()
toString() 方法返回一个表示该对象的字符串。默认情况下,toString() 方法被每个 Object 对象继承。
为了每个对象都能通过 Object.prototype.toString() 来检测,需要以 Function.prototype.call() 或者 Function.prototype.apply() 的形式来调用,传递要检查的对象作为第一个参数,称为 thisArg。
var toString = Object.prototype.toString;
toString.call(1) //"[object Number]"
toString.call(true) //"[object Boolean]"
toString.call('string') //"[object String]"
toString.call({ name:'zhangsan', age: 18 }) //"[object Object]"
toString.call([1,2,3]) //"[object Array]"
toString.call(undefined) //"[object Undefined]"
toString.call(null) //"[object Null]"
toString.call(new Date); //"[object Date]"
constructor
constructor是原型prototype的一个属性,当函数被定义时候,js引擎会为函数添加原型prototype,并且这个prototype中constructor属性指向函数引用, 因此重写prototype会丢失原来的constructor。对于原始数据类型来说,constructor属性是只读的。
注:null和undefined没有构造函数,所以当检测 null 或者 undefined 类型的 constructor 属性时,js会报错!
const obj = {name: 'c',age: 21}
const arr = [1,2,3]
const str = 'cc'
const boolean = true
const number = 1
const func = () => {console.log(1)}
const temp_undefined = undefined
const temp_null = null
console.log(obj.constructor == Object) // true
console.log(arr.constructor == Array) // true
console.log(str.constructor == String) // true
console.log(boolean.constructor == Boolean) // true
console.log(number.constructor == Number) // true
console.log(func.constructor == Function) // true
console.log(temp_undefined.constructor) // 报错
console.log(temp_null.constructor) // 报错
原型与原型链
参考文章:JavaScript深入之从原型到原型链
什么是原型?
每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型“继承”属性。
每个函数都有一个prototype属性,称为显式原型。
每个实例对象都有一个__proto__属性,称为隐式原型。
prototype 是函数才会有的属性, 而__proto__ 是几乎所有对象都有的属性。
例子:
function Person() {
}
// prototype是函数才会有的属性
Person.prototype.name = 'Kevin';
let person1 = new Person();
let person2 = new Person();
console.log(person1.name); //Kevin
console.log(person2.name); //Kevin
prototype
每个函数都有一个prototype属性,就是我们例子中看到的prototype。
那这个函数的prototype属性到底指向的是什么呢?是这个函数的原型吗?
其实,函数的prototype属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型,也就是例子中的person1和person2的原型。
proto
这是每一个JavaScript对象(除了null)都具有的一个属性,叫__proto__,这个属性会指向该对象的原型。
function Person() {
}
let person = new Person();
console.log(person.__proto__ === Person.prototype); // true
既然实例对象和构造函数都可以指向原型,那么原型是否有属性指向构造函数或者实例呢?
constructor
指向实例倒是没有,因为一个构造函数可以生成多个实例,但是原型指向构造函数倒是有的,这就要讲到第三个属性:constructor,每个原型都有一个 constructor 属性指向关联的构造函数。
function Person() {
}
console.log(Person === Person.prototype.constructor); // true
实例与原型
当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。 举个例子:
function Person() {
}
Person.prototype.name = 'Kevin';
var person = new Person();
person.name = 'Daisy';
console.log(person.name) // Daisy
delete person.name;
console.log(person.name) // Kevin
在这个例子中,我们给实例对象 person 添加了 name 属性,当我们打印 person.name 的时候,结果自然为 Daisy。
但是当我们删除了 person 的 name 属性时,读取 person.name,从 person 对象中找不到 name 属性就会从 person 的原型也就是 person.__proto__ ,也就是 Person.prototype中查找,幸运的是我们找到了 name 属性,结果为 Kevin。
但是万一还没有找到呢?原型的原型又是什么呢?
原型的原型
在前面,我们已经讲了原型也是一个对象,既然是对象,我们就可以用最原始的方式创建它,那就是:
let obj = new Object();
obj.name = 'Kevin';
console.log(obj.name); //Kevin
其实原型对象就是通过 Object 构造函数生成的,结合之前所讲,实例的 __proto__ 指向构造函数的 prototype 。
原型链
那Object.prototype的原型呢?答案是null。
Object.prototype.__proto__ 的值为 null 跟 Object.prototype 没有原型,其实表达了一个意思。
所以查找属性的时候查到 Object.prototype 就可以停止查找了。
由相互关联的原型组成的链状结构就是原型链,原型链通过__proto__链接起来。
继承有哪几种方式
- 原型链继承
- 借用构造函数(经典继承)
- 组合继承(原型链继承和经典继承组合)
- 原型式继承
- 寄生式继承
- 寄生组合式继承
作用域
- 全局作用域
- 函数作用域
- 块级作用域
var、let、const的区别
var声明的变量会挂载在window上,存在变量提升let和const声明形成块作用域,不存在变量提升。 同一个作用域下let和const不能声明同名变量,而var可以const一旦声明必须赋值,不能使用null占位;声明后不能再修改;如果声明的是引用类型数据,可以修改其属性
变量提升、暂时性死区
console.log(name); // undefined
var name = 'Kevin'
console.log(age); // Uncaught ReferenceError: age is not defined
let age = 18;
变量提升
var命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined。
也就是例子中的变量name在代码开始运行时,name就已经存在了,但是没有值,输出undefined。
暂时性死区
如果区块中存在let或const命令,这个区块对这些声明的变量,从一开始就形成了封闭作用域。在使用命令声明变量之前,该变量都是不可用的。在语法上,称为“暂时性死区”。
例子中的age,在声明它之前,age是不存在的,这时候如果用到它,就会抛出一个错误。
new操作符
- 创建一个空的简单JavaScript对象(即{});
- 链接该对象(即设置该对象的构造函数)到另一个对象 ;
- 将步骤1新创建的对象作为this的上下文 ;
- 如果该函数没有返回对象,则返回this。
EventLoop
- 指向全局Script同步代码,这些同步代码有一些是同步语句,有一些是异步语句(比如setTimeout等)
- 全局Script代码执行完毕后,调用栈Stack会清空
- 从微队列microtask queue中取出位于队首的回调任务,放入调用栈Stack中执行,执行完后microtask queue长度减1
- 继续取出位于队首的任务,放入调用栈Stack中执行,以此类推,直到把微任务队列中的所有任务都执行完毕。注意:如果在执行微任务的过程中,又产生了微任务,那么会加入到队列的末尾,也会在这个周期被调用执行
- 微任务队列中的所有任务都执行完毕,此时微任务队列对空队列,调用栈Stack也为空
- 取出宏队列中位于队首的任务,放入Stack中执行
- 执行完毕后,调用栈Stack为空
- 一直重复3-7步骤
这就是浏览器的事件循环Event Loop