1、数据类型有那些
数据类型可以分为基本类型和引用类型。
基本类型:number、string、boolean、undefined、null、bigint、symbol
引用类型:object(array、function、date 这些是类,不是数据类型注意)
2、数据类型的一些判断方法
在开发过程中有时候需要对这些类型进行一些判断,下面介绍一些常用的判断方法,以及各自的优点和弊端。
typeof
优点:能够快速区分基本数据类型 缺点:不能将Object、Array和Null区分,都返回object
-
typeof的作用?区分数据类型,可以返回7种数据类型:
number、string、boolean、undefined、object、function,以及ES6新增的symbol -
typeof能正确区分数据类型吗?不能。对于基本类型,除
null都可以正确判断;对于引用类型,除function外,都会返回"object" -
typeof注意事项typeof返回值为string格式,注意类似这种考题:typeof(typeof(undefined)) -> "string"typeof未定义的变量不会报错,返回"undefiend"typeof(null) -> "object": 遗留已久的bugtypeof无法区别数组与普通对象:typeof([]) -> "object"typeof(NaN) -> "number"
4.typeof(null)->"object" 这是为什么?
这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象然而 null 表示为全零,所以将它错误的判断为 object。
instanceof
优点:能够区分Array、Object和Function,适合用于判断自定义的类实例对象 缺点:Number,Boolean,String基本数据类型不能判断。
instanceof是通过原型链来判断的,但是对于对象来说,Array也会被转换成Object.
instanceof判断对象的原型链上是否存在构造函数的原型。只能判断引用类型。instanceof常用来判断A是否为B的实例
手动实现
function instance_of(Case, Constructor) {
// 基本数据类型返回false 兼容一下函数对象
if ((typeof(Case)!='object' && typeof(Case)!='function')||Case == 'null')return false;
let CaseProto = Object.getPrototypeOf(Case);
while (true) {
// 查到原型链顶端,仍未查到,返回false
if (CaseProto == null) return false;
// 找到相同的原型
if (CaseProto === Constructor.prototype) return true;
CaseProto = Object.getPrototypeOf(CaseProto);
}}
Object.prototype.toString.call()
优点:精准判断数据类型
缺点:写法繁琐不容易记,推荐进行封装后使用
toString.call(()=>{}) // [object Function]
toString.call({}) // [object Object]
toString.call([]) // [object Array]
toString.call('') // [object String]
toString.call(22) // [object Number]
toString.call(undefined) // [object undefined]
toString.call(null) // [object null]
其他的一些
判断数组Array.isArray
3、原型链是什么?
假设我们有一个普通对象 x={} ,这个 x 会有一个隐藏属性,叫做 __?????__ ,这 个属性会指向 Object.prototype ,即
x.__?????__ === Object.prototype // 原型
此时,我们说 x 的原型 是 Object.prototype,或者说 Object.prototype 是 x 的原型。
而这个 __?????__ 属性的唯一作用就是用来指向 x 的原型的。
如果没有 __?????__ 属性,x 就不知道自己的原型是谁了。
为什么我用问号来表示?因为这样你不容易晕
接下来我来说说原型链,我还是举例说明吧。
假设我们有一个数组对象 a=[] ,这个 a 也会有一个隐藏属性,叫做 __?????__ , 这个属性会指向 Array.prototype ,即
a.__?????__ === Array.prototype
此时,我们说 a 的原型是 Array.prototype ,跟上面的 x 一样。但又有一点不一样, 那就是 Array.prototype 也有一个隐藏属性 __?????__ ,指向 Object.prototype , 即
// 用 x 表示 Array.prototype x.__?????__ === Object.prototype
这样一来,a 就有两层原型:
1. a 的原型是 Array.prototype
2. a 的原型的原型是 Object.prototype 于是就通过隐藏属性 __?????__ 形成了一个链条:
a ===> Array.prototype ===> Object.prototype
怎样做(如何改写原型):
看起来只要改写 x 的隐藏属性 __?????__ 就可以改变 x 的原型(链)
x.__?????__ = 原型
但是这不是标准推荐的写法,为了设置 x.__?????___ ,推荐的写法是
const x = Object.create(原型)
// 或
const x = new 构造函数() // 会导致 x.__?????__ === 构造函数.prototype
原型链解决了什么问题:
在没有 Class 的情况下实现「继承」。
以 a ===> Array.prototype ===> Object.prototype 为例,
我们说:
1. a 是 Array 的实例,a 拥有 Array.prototype 里的属性
2. Array 继承了 Object(注意专业术语的使用)
3. a 是 Object 的间接实例,a 拥有 Object.prototype 里的属性 这样一来,a 就既拥有 Array.prototype 里的属性,又拥有 Object.prototype 里的属性。
优点:简单优雅
缺点:跟class相比较,不支持私有属性。
其他博客:推荐
4、JS的new做了些什么
1、创建临时对象/新对象
2、绑定原型
3、指定this=临时对象
4、执行构造函数
5、返回临时对象
5、JS 的立即执行函数是什么
是什么:声明一个匿名函数,然后立即执行它。这种做法就是立即执行函数。
怎么做(代码演示):
(function(){alert('我是匿名函数')} ()) // 用括号把整个表达式包起来
(function(){alert('我是匿名函数')}) () // 用括号把函数包起来
!function(){alert('我是匿名函数')}() // 求反,我们不在意值是多少,只想通过语法
+function(){alert('我是匿名函数')}()
-function(){alert('我是匿名函数')}()
~function(){alert('我是匿名函数')}()
void function(){alert('我是匿名函数')}()
new function(){alert('我是匿名函数')}()
var x = function(){return '我是匿名函数'}(
解决了什么问题:在ES6之前,只能通过它来创建局部作用域。
优点:兼容性好
缺点:写法比较丑
如何解决缺点:使用ES6的block+let语法
6、JS 的闭包是什么
是什么:闭包是 JS 的一种语法特性。//闭包=函数+自由变量
对于一个函数来说,变量分为:全局变量、本地变量、自由变量
eg:
let count
function add (){ // 访问了外部变量的函数
count += 1
}
把上面代码放在「非全局环境」里,就是闭包。
注意,闭包不是 count,闭包也不是 add,闭包是 count + add 组成的整体。
怎么制造一个「非全局环境」呢?答案是立即执行函数:
const x = function (){
var count
function add (){ // 访问了外部变量的函数
count += 1
}
}()
但是这个代码什么用也没有,所以我们需要 return add ,即
const add2 = function (){
var count
return function add (){ // 访问了外部变量的函数
count += 1
}
}()
此时 add2 其实就是 add,我们可以调用 add2
add2()
// 相当于
add()
// 相当于
count += 1
至此,我们就实现了一个完整的「闭包的应用」
注意:闭包 ≠ 闭包的应用,但面试官问你「闭包」的时候,你一定要答「闭包 的应用」,这是规矩
解决了什么问题:
1. 避免污染全局环境。(因为用的是局部变量)
2. 提供对局部变量的间接访问。(因为只能 count += 1 不能 count -= 1)
3. 维持变量,使其不被垃圾回收。
优点:简单好用
缺点:闭包如果使用不当可能造成内存泄露。
7、JS如何实现类
方法一:使用原型
function Dog(name){
this.name=name;
this.legsName=4;
}
Dog.prototype.kind='狗';
Dog.prototype.say=function(){
console.log(`汪汪汪~ 我是${this.name},我有${this.legsNumber}条腿。`)
}
Dog.prototype.run=function(){
console.log(`${this.legsNumber}条腿跑起来。`)
}
const dog=new Dog('旺财')
dog.run();
dog.say();
方法二:使用class
class Dog{
kind='狗'// 等价于在 constructor 里写 this.kind = '狗'
constrouctor(name){
//this.kind = '狗' this.name=name;
this.legsName=4;
};
say(){
console.log(`汪汪汪~ 我是${this.name},我有${this.legsNumber}条腿。`)};
run(){
console.log(`${this.legsNumber}条腿跑起来。`)}
}
const dog=new Dog('旺财')
dog.run();
dog.say();
8、JS 如何实现继承
方法一: 使用原型链
function Animal(legsNumber){
this.legsNumber=legsNumber
}
Animal.prototype.run=function(){ console.log(`${this.legsNumber}条腿跑起来。`)
}function Dog(name){
this.name=name;
Animal.call(this,4)//关键代码一
}
Dog.prototype.__proto__=Animal.prototype;//主要代码二 但是这个代码如果被禁用了
//等价于
var f=function(){}
f.prototype=Animal.prototype
Dog.prototype=new f()
Dog.prototype.kind = '狗'
Dog.prototype.say = function(){
console.log(`汪汪汪~ 我是${this.name},我有${this.legsNumber}条腿。`)
}
const d1 = new Dog('啸天') // Dog 函数就是一个类
console.dir(d1)
方法二:使用class
class Animal{
constructor(legsNumber){
this.legsNumber=legsNumber
}
}
class Dog extends Animal{
constructor(){
super(4);
this.name=name
}
say(){
console.log(`汪汪汪~ 我是${this.name},我有${this.legsNumber}条腿。`)
}
}
const d1 = new Dog('啸天')
console.dir(d1)