1.数据类型
1-1. 有哪些数据类型?
javascript的数据类型有8种,他们分别是Number、String、Boolean、Undefined、Null、Object、Symbol和BigInt。其中Symbol和BigInt是ES6之后才新加的。
Object类型的主要有对象(object)、函数(function)和数组(array)。
其中Number、String、Boolean、Undefined、Null、Symbol、BigInt是原始值,Object是引用数据类型。原始值就是最简单的数据,按值访问,存储在栈区中;引用类型则是由多个值构成的对象,占据空间大,大小不固定,存储在堆区中,然后将堆区的地址存放在栈区,我们操作引用数据类型时,得到的是栈区的内存地址。
1-2.判断数据类型的方式
(1)typeof
console.log(typeof 1) //number
console.log(typeof 'aaa22') //string
console.log(typeof true) //boolean
console.log(typeof undefined) //undefined
console.log(typeof null) //object
console.log(typeof {}) //object
console.log(typeof function(){}) //function
console.log(typeof []) //object
let a;
let A = Symbol();
console.log(a); //undefined
console.log(A) //Symbol()
typeof可以用于判断任何类型的数据,但是其中null、数组、对象都是输出object,函数输出function,未定义的返回undefined,Symbol返回Symbol()
(2)instanceof
instanceof适用于判断引用数据类型,原理是查找该类型的prototype是否在待检测数据的原型链上。
console.log(1 instanceof Number) //false
console.log('aaa22' instanceof String) //false
console.log(true instanceof Boolean) //false
console.log({} instanceof Object) //true
console.log(function(){} instanceof Function) //true
console.log(function(){} instanceof Object) //true
console.log([] instanceof Array) //true
console.log([] instanceof Object) //true
其中原始值判断都为false。 其中instanceof还可以用于检测对象的继承关系。
(3)constructor
constructor的作用一般有两个,一是通过它可以查找该对象的构造函数,二是判断数据类型。
let num = 10;
let str = 'aaa';
let boo = true;
console.log([].constructor === Array); //true
console.log({}.constructor === Object); //true
console.log(num.constructor === Number); //true
console.log(str.constructor === String); //true
console.log(boo.constructor === Boolean); //true
console.log(unde.constructor == undefined);//Cannot read property ‘constructor’ of undefined
console.log(nul.constructor == Null);//Cannot read property ‘constructor’ of null
疑问:为什么基本数据类型也可以通过constructor判断数据类型?
这是因为在js里万物皆对象,因为Number、Boolean、String等原始值有两种定义方式,一是字面量形式
let a = 10;,二是使用new关键字构造 let a = new Number(10);,我们一般习惯于使用字面量形式创建,但是使用字面量形式创建时,JS内部会自动调用该类型的构造函数生成对象(不然原始数据类型哪能使用方法呢?详情后面到对象与原型链讲解)。
(4) Object.prototype.toString.call(obj)
toString是Object的一个方法,会返回反映这个对象的字符串。JS中所有的构造器Number,Array,Function等都会继承并重写这个方法。
console.log(Object.prototype.toString.call(‘1’));// [object String]
console.log(Object.prototype.toString.call(1);// [object Number]
console.log(Object.prototype.toString.call(true);// [object Boolean]
console.log(Object.prototype.toString.call(undefined);// [object Undefined]
console.log(Object.prototype.toString.call(null);// [object Null]
console.log(Object.prototype.toString.call(function(){});// [object Function]
console.log(Object.prototype.toString.call(new Date());// [object Date]
console.log(Object.prototype.toString.call([1]);// [object Array]
console.log(Object.prototype.toString.call(new RegExp());// [object RegExp]
2.声明方法
JS声明变量的方法有三种,分别是var、let、const,其中let和const是ES6之后新增的,他们和var的主要区别就是多了块级作用域,少了变量提升。
(1)var
使用var声明的变量会成为包含它的函数的局部变量。比如使用var在一个函数内部定义一个变量,就意味着变量在函数退出时被销毁;
function test() {
var name = 'ly';
}
test();
console.log(name) //出错
但是在函数内部去掉var就成了全局变量。
同时var会存在着变量提升问题,比如:
console.log(name) //undefined
var name = 'ly'
var还可以多次定义:
var name = 'ly';
var name = 'yl';
var name = '124';
console.log(name) //124
(2)let
let和var的声明方式差不多,但是let声明的范围是块级作用域,而var声明的是函数作用域。
let不存在变量提升,如果在声明前使用变量会存在暂时性死区:
console.log(name) //ReferenceError:name is not defined
let name = 'ly'
块级作用域:(块级作用域是函数作用域的子集)
{
let name = 'ly';
}
console.log(name); //ReferenceError: name is not defined
不能重复定义:
let name = 'ly';
let name = 'yl'; //SyntaxError: Identifier 'name' has already been declared
console.log(name);
(3)const
const的行为与let基本相同,唯一一个区别就是使用const声明变量时必须同时初始化变量,且定义后不能再修改。
const name = 'ly';
name = 'yl'; //Assignment to constant variable.
console.log(name);
3.执行上下文与作用域
变量和函数的执行上下文决定了他们可以访问哪些数据,以及他们的行为。每一个上下文都有一个关联的变量对象,而这个上下文中定义的所有变量和函数都存在于这个对象上。
每个函数都有自己的上下文。上下文在其所有代码执行完毕后就会销毁,包括定义在它上面的变量和函数(JS的垃圾回收机制)。
js中作用域分为:
全局作用域:
(1)最外层函数和最外层函数外通过var来定义的变量(全局变量、全局函数)
(2)在函数内部不通过var定义的变量也称为全局变量
局部作用域:
(1)在函数内通过var来定义的变量,以及内部定义的函数
(2)函数的参数具有局部作用域
1、变量对象(variable object) 变量对象的属性由 变量和 函数声明构成。在函数上下文情况下,参数列表也会被加入到变量对象中作为属性。 (注:只有 函数声明会被加入到变量对象中,而 函数表达式则不会)
2、活动对象(activation object) 活动对象由特殊对象 arguments 初始化而成。随后,他被当做变量对象用于变量初始化。
上下文在执行的时候,会创建变量对象的作用域链。这个作用域链决定了各级上下文中的代码在访问变量和函数时的顺序。代码正在执行的上下文中的变量对象始终位于作用域链的最前端。如果上下文是函数,则其活动对象用作变量对象。活动对象最初只有一个定义变量:arguments。作用域链中的下一个变量对象来自包含上下文,再下一个对象来自再下一个包含上下文,以此类推直至全局上下文。
JavaScript上每一个函数执行时,会先在自己创建的AO(活动对象)上找对应属性值。若找不到则往父函数的AO(活动对象)上找,再找不到则再上一层的AO(活动对象),直到找到大boss:window(全局作用域)。 而这一条形成的“AO链” 就是JavaScript中的作用域链。
var color = 'red';
function changeColor() {
if(color === 'red') {
color = 'blue'
} else {
color = 'red'
}
}
changeColor();
这个例子,函数changeColor()的作用域链包含两个对象:一个是它自己的变量对象(arguments,每个函数都有它,会自动创建),一个是全局上下文的变量对象。这个函数之所以可以访问变量color,就是因为可以在作用域链中找到它。