JS数据类型
【基本数据类型】、【引用数据类型】
区别:
【基本数据类型】:直接存储在栈(stack)
中,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储。
【引用数据类型】:同时存储在栈(stack)和堆(heap)
中,占据空间大、大小不固定。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
包括:
【基本数据类型】:String、Number、Boolean、Null、Undefined【Symbol(es6新增,表示独一无二的值)、BigInt(es10新增)】【5个常用,2个不常用,共7个】
【引用数据类型】:Object 【包括:Object ,Array,Function,Date…】【理论上就1个】
所以可以回答说JS数据类型一共8种
JS数据类型的判断方法
【 typeof 】、【 instanceof 】、【 Object.prototype.toString.call() 】
typeof:
只能检测出string、number、boolean、undefined、function、object、【symbol、bigint】
(6种常用,2种不常用)
注意:【 null => object 】,其他引用数据类型,除了【 function 】,其他都是输出【 object 】
console.log(typeof ('123')) // string
console.log(typeof (123)) // number
console.log(typeof (true)) // boolean
console.log(typeof (null)) // ***object***
console.log(typeof (undefined)) // undefined
console.log(typeof (Symbol())) // symbol
console.log(typeof (BigInt(9007199254740991))) // bigint
console.log(typeof ({})) // object
console.log(typeof ([])) // object
console.log(typeof (function(){})) // function
console.log(typeof (new Date())) // object
instanceof:
这玩意用来判断JS数据类型,可以说是非常鸡肋,但是面试要问
注意instanceof
可以准确的判断 【引用数据类型】,但是却无法判断【基本数据类型】
console.log('123' instanceof String); // false
console.log(123 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log([] instanceof Array); // true
console.log(function () { } instanceof Function); // true
console.log({} instanceof Object); // true
console.log(new Date() instanceof Date); // true
Object.prototype.toString.call()
非常牛逼,什么类型都能判断出来,但平时项目中,因为太长不用…总体出场率并没有typeof高
console.log(Object.prototype.toString.call('123')) // [object String]
console.log(Object.prototype.toString.call(123)) // [object Number]
console.log(Object.prototype.toString.call(true)) // [object Boolean]
console.log(Object.prototype.toString.call(null)) // [object Null]
console.log(Object.prototype.toString.call(undefined)) // [object Undefined]
console.log(Object.prototype.toString.call(Symbol())) // [object Symbol]
console.log(Object.prototype.toString.call(BigInt(1))) // [object BigInt]
console.log(Object.prototype.toString.call({})) // [object Object]
console.log(Object.prototype.toString.call([])) // [object Array]
console.log(Object.prototype.toString.call(function(){})) // [object Function]
console.log(Object.prototype.toString.call(new Date())) // [object Date]
可以简单的封装成一个函数
function myTypeOf(value) {
return Object.prototype.toString.call(value).slice(8, -1)
}
console.log(myTypeOf('123')) // String
console.log(myTypeOf(123)) // Number
console.log(myTypeOf(true)) // Boolean
console.log(myTypeOf(null)) // Null
console.log(myTypeOf(undefined)) // Undefined
console.log(myTypeOf(Symbol())) // Symbol
console.log(myTypeOf(BigInt(1))) // BigInt
console.log(myTypeOf({})) // Object
console.log(myTypeOf([])) // Array
console.log(myTypeOf(function () { })) // Function
console.log(myTypeOf(new Date())) // Date
JS数据类型的相互转换
JS类型转换只有三种情况:
【1.】 转换为布尔值(调用Boolean()
方法)
【2.】 转换为数字(调用Number()
、parseInt()
和parseFloat()
方法)
【3.】 转换为字符串(调用.toString()
或者String()
方法)
注意!!!
null、underfined 没有 .toString 方法
String(null) // null
String(undefined) // undefined
<-- 常见的 布尔类型 转换 -->
<-- 只有这6个值转化为boolean时为false,其他的都是true,{}、[]也是true -->
+0(0)、-0、""、NAN、null、undefined
<-- 常见的 数字 转换 -->
Number(null) // 0
parseInt(null) // NAN
parseFloat(null) // NAN
Number({}) // NAN
Number([]) // 0
Number([123]) // 123
Number([123,456]) // NAN
<-- 常见的 字符串 转换 -->
['刻晴', 101, true, null, undefined].toString() // 刻晴,101,true,, null和undefined无法转化,所以为空
{}.toString() // [object Object]
undefined 与 undeclared 的区别
已在作用域中声明但还没有赋值的变量,是 undefined。相反,还没有在作用域中声明过的变量,是 undeclared 的
null 和 undefined 的区别?
undefined:表示未定义,一般变量声明了但还没有定义的时候会返回 undefined
null:表示空,无,就是啥也没有
其实 null 不是对象,虽然 typeof null 会输出 object,但是这只是 JS 存在的一个悠久 Bug。
在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,
000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。
虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。
this的理解
this的四种绑定规则
- 默认绑定
- 隐式绑定
- 显示绑定
- new绑定
【1 .】默认绑定
全局this
默认指向window
(其实是指向全局对象,但是这里默认是浏览器,浏览器的全局对象是window
,如果是在node环境中,或者严格模式下,就不是指向window
了)
console.log(this === window) // true
函数独立调用中的this
,默认也是指向window
的,立即执行函数同理(无论这个立即执行函数在什么地方,函数中的this
都指向window
)
【函数独立调用:意思是调用该函数时,直接使用testThis()
的形式进行调用,或者是window.testThis()
】
普通的函数独立调用
function testThis() {
console.log(this === window); // true
}
立即执行函数(无论在哪里,都指向window,除非主动使用call,apply等更改)
function testThis() {
console.log(this === window); // true
(function () {
console.log(this === window); // true
})()
}
testThis();
let obj = {
a: '123',
foo: (function () {
console.log(this === window);
})(),
};
【2 .】隐式绑定【存在(隐式丢失,参数赋值)】
通过【对象】
的【属性】
的方式调用该方法时,该方法中的this
,指向该调用者,
也就是我们常说的谁调用,就指向谁(函数的this指向大概率跟执行方式有关)
【this是在函数执行时,产生的一个AO/GO(AO函数执行的上下文,GO是一个global),在AO中都会有相应的this指向的问题,也就是说,只有函数执行this指向才有意义,函数不执行,this就没有意义
预编译时,这个this指向就已经确定下来了,函数在执行时才会产生自身的this执行
意思就是,每一个this执行都是函数执行时,才会产生的】
<!--这是一个最简单的【隐式绑定】,此时this指向obj本身,也就是谁调用了,就指向谁 -->
let obj = {
a: 'zhangsan',
foo: function () {
console.log(this);
},
};
obj.foo()
// 一个稍微复杂点的【隐式绑定】
var obj = {
a: 2,
foo: function () {
console.log(this); // 指向obj
function testThis() {
console.log(this); // 指向window
}
testThis(); // 被认定为函数的独立调用
(function () {
console.log(this); // 指向window
})();
// 当函数执行时,导致函数被定义,并抛出(闭包,闭包是一种Js现象,不是语法规则)
function closure() {
console.log(this); // 指向window
}
return closure;
},
};
obj.foo(); // obj调用其中的属性,而这个属性是一个函数,属于,谁调用指向谁
obj.foo()(); // 此时函数中有闭包,整个函数执行完,返回一个函数,也就是这里的obj.foo()();其实就是closure()属于独立调用
隐式调用的特殊情况【隐式丢失,参数赋值】
function foo() {
console.log(this);
}
var obj = {
a: 2,
foo: foo,
};
var bar = obj.foo; // 将对象的属性值赋值给bar,对象的属性值又指向foo函数,这里只是简单的赋值,并没有函数执行
bar() // 所以这里执行时,就等于foo(),所以指向window
var a = 0;
function foo() {
console.log(this);
}
function bar(fn) {
fn();
}
var obj = {
a: 2,
foo: foo,
};
// 参数由实参变为形参的过程,是一个【浅拷贝】的过程
bar(obj.foo); // 指向window
【3 .】显示绑定【call,apply,bind(区别在于传递参数的不同)】
两类参数,第一个是要改变为this指向的对象
,第二个是要传递的参数
当第一个参数不是对象时,如('123',1,true)
,会指向他们的包装类String {"123"}
,Number {1}
,Boolean {true}
但是null和undefined
是没有包装类的,此时会指向默认的window
// 一个最简单的【显示绑定】
function testThis() {
console.log(this);
}
let obj = {
a: '123',
};
testThis.call(obj);
function foo(a, b, c) {
console.log(this);
}
var obj = {
a: 2,
foo: foo,
};
var bar = obj.foo; // 将对象的属性值赋值给bar,对象的属性值又指向foo函数,这里只是简单的赋值,并没有函数执行
obj.foo(1, 2, 3); // 隐式绑定,指向 obj {a: 2, foo: ƒ}
bar.call('123', 1, 2, 3); // 显示绑定,指向包装类String {"123"}
bar.call(1, 1, 2, 3); // 显示绑定,指向包装类Number {1}
bar.call(true, 1, 2, 3); // 显示绑定,指向包装类Boolean {true}
bar.apply(obj, [1, 2, 3]); // 显示绑定,参数不同 指向 obj {a: 2, foo: ƒ}
bar.bind(obj)(1, 2, 3); // 显示绑定,参数不同 指向 obj {a: 2, foo: ƒ}
【4 .】new绑定
new一个对象的过程(对象实例化的过程)
1. 创建一个空对象
2. 将this指向这个空对象{}
3. 执行构造函数中的代码,也就是对this进行赋值
4. 将这个this返回回去(这个this就是函数实例化执行的结果,也就是说这个this就是实例化之后的对象,因为将这个this赋值给这个对象了嘛)
// 一个简单的,构造函数+实例化过程
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
var person = new Person('zhangsan', '24', 'male');
实例化过程中最重要的就是,构造函数的return值(但是一般我们也不会手动指定返回值,因为构造函数本身会隐式的返回this)
1. 这里手动添加的返回值如果是{},[],function,则会改变this的指向
2. 如果是其他值,那么任然会指向实例化后的对象本身
function Person(name, age, sex) {
this.a = 1;
return {
name: 2,
};
}
var person = new Person('zhangsan', '24', 'male');
console.log(person); // {name: 2}
function Person(name, age, sex) {
this.a= 1;
return 123;
}
var person = new Person('zhangsan', '24', 'male');
console.log(person) // Person {a: 1}
this的绑定优先级
new绑定 > 显示绑定 > 隐式绑定 > 默认绑定
练习
// 那么我们可以做一道题检测一下
var number = 5;
var obj = {
number: 3,
fn1: (function () {
var number;
this.number *= 2;
number = number * 2;
number = 3;
return function () {
var num = this.number;
this.number *= 2;
console.log(num);
number *= 3;
console.log(number);
};
})(),
};
var fn1 = obj.fn1;
fn1.call(null);
obj.fn1();
解析
var number = 5;
var obj = {
number: 3,
fn1: (function () {
var number; // 定义一个number
this.number *= 2; // 因为是自执行函数,所以指向window,所以this.number = this.number*2值得是5*2 = 10
number = number * 2; // number只被定义,没被赋值,所以是undefined,undefined*2=NAN
number = 3; // 但是这一步number又被重新赋值,所以number = 3
return function () {
var num = this.number;
this.number *= 2;
console.log(num);
number *= 3;
console.log(number);
};
})(),
};
// 这里将对象的一个属性,赋值给fn1,但是这是个自执行函数,在赋值后直接就可以执行了,此时,因为他是自执行函数(注意,立即执行函数作为属性的值时,在对象定义完成后就会直接执行,但此时里面是个闭包函数,所以没有任何值输出)
// 自执行函数无论放在哪里都执行window,所以这一步,fn1中的this执行window
var fn1 = obj.fn1;
// 这里使用【显示绑定】,将闭包函数指向null,上面提到,null是没有包装类的,所以会指向默认的window
// 这里是纯执行闭包中的代码
// function () {
// var num = this.number; num = 10,因为外层函数已经将window中的number变成10了
// this.number *= 2; this.number = 20,这里又把window中的number变成20
// console.log(num); 输出 10
// number *= 3; number是外层函数定义的值,是3,这里3*3 = 9
// console.log(number); 所以这里输出9
// };
fn1.call(null); // 10 9
// 这里是典型的【隐式绑定】所以这里的this执行的就是obj这个对象本身
// 这里是纯执行闭包中的代码
// function () {
// var num = this.number; this指向obj本身,num = 3
// this.number *= 2; this.number = this.number * 2 = 6这里又把obj中的number变成6
// console.log(num); num = 3,所以输出3
// number *= 3; 因为这个闭包的原因,外层调用的number并没有被释放,所以number = 9 * 3 = 27
// console.log(number); 这里输出27
// };
obj.fn1();