JavaScript基础

188 阅读6分钟

1、数据类型有那些

数据类型可以分为基本类型和引用类型。

基本类型:number、string、boolean、undefined、null、bigint、symbol

引用类型:object(array、function、date 这些是类,不是数据类型注意)

2、数据类型的一些判断方法

在开发过程中有时候需要对这些类型进行一些判断,下面介绍一些常用的判断方法,以及各自的优点和弊端。

typeof  

优点:能够快速区分基本数据类型 缺点:不能将Object、Array和Null区分,都返回object

  1. typeof的作用?

    区分数据类型,可以返回7种数据类型:number、string、boolean、undefined、object、function ,以及 ES6 新增的 symbol

  2. typeof 能正确区分数据类型吗?

    不能。对于基本类型,除 null 都可以正确判断;对于引用类型,除 function 外,都会返回 "object"

  3. typeof 注意事项

    • typeof 返回值为 string 格式,注意类似这种考题: typeof(typeof(undefined)) -> "string"
    • typeof 未定义的变量不会报错,返回 "undefiend"
    • typeof(null) -> "object": 遗留已久的 bug
    • typeof无法区别数组与普通对象: 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.

  1. instanceof 判断对象的原型链上是否存在构造函数的原型。只能判断引用类型。
  2. 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)