前端面试题:js面试题(上)

1、JS数据类型有哪些?

基本类型:string、number、boolean、undefined、null、(symbol、bigint)
引用类型:object

NaN是一个数值类型,但是不是一个具体的数字。

 2、null和undefined的区别

1. 作者在设计js的都是先设计的null(为什么设计了null:最初设计js的时候借鉴了java的语言)
2. null会被隐式转换成0,很不容易发现错误。(null像在java里一样,被当成一个对象,作者觉得表示“无”的值最好不是对象)
3. 先有null后有undefined,出来undefined是为了填补之前的坑。

具体区别:JavaScript的最初版本是这样区分的:null是一个表示"无"的对象(空对象指针),转为数值时为0;undefined是一个表示"无"的原始值,转为数值时为NaN。

null:

  • 是一个值,指示变量不指向任何对象。

  • 是对象类型。

  • 表示 null、null或不存在的引用。

  • 表示没有变量值。

  • 使用原始操作转换为 0。

undefined:

  • 是表示已声明但没有值的变量的值

  • 是未定义的类型。

  • 表示没有变量。

  • 使用原始操作转换为 NaN。

 console.log( true + 1 );                 //2
console.log( 'name'+true );              //nametrue
console.log( undefined + 1 );         //NaN
console.log( typeof undefined ); //undefined

console.log( typeof(NaN) );       //number
console.log( typeof(null) );      //object

3、 JS作用域考题

 步骤:

1、作用域链:内部可以访问外部的变量,但是外部不能访问内部的变量。
     注意:如果内部有,优先查找到内部,如果内部没有就查找外部的。

2、变量提升的机制【变量悬挂声明】

3、优先级:声明变量 > 声明普通函数 > 参数 > 变量提升

注意:

1、除了函数外,其他没有作用域(if,for,switch),

2、声明变量是用var还是没有写(window.)

3、普通函数中的this是window,实例化了才是实例对象

var b = 1;

function c(){

//b在函数内部

//变量提升

//var b=undefined

//fun b(){}

//预编译:b=undefined——>b=f b(){}
       console.log( b );//f b(){}
       var b = 2;//b=2,声明变量
       console.log( b );//2

//普通声明函数是不看写函数的时候顺序

        function b(){

                return 1111;

        }
}
c();

 var name = 'a';
(function(){

//name函数内变量提升
    if( typeof name == 'undefined' ){//不过条件真假,该 提提声

        console.log(typeof name)//undefined
        var name = 'b';//在函数作用域内
        console.log('111'+name);//内部有,可在里面找
    }else{
        console.log('222'+name);
    }
})()

//打印"111b"

//tetst1

function fun(){

    a=20;//相当于window.a

    console.log(a)//20

}

fun()

//test2

function fun(){

   

    console.log(a)//报错:Uncaught ReferenceError: a is not defined

    a=20;

}

fun()

//tets3

function fun(){

     //var a=undefined

    a=20;//相当于再次赋值了

    console.log(a)//20,所以不是undefined

    var a=10;

}

fun()

//前面无let,var,如a=3,相当于window.a=3

var a=5;

function fun(){

    a=0;//相当于再次赋值

    console.log(a)//0

    console.log(this.a)//5

    var a;

    console.log(a)//0

}

fun()

//外部作用域不能访问内部作用域

//var a=undefined

var f=true;

if(f==true){

    var a=10;//提升至外面

}

function fun(){

    var b=20;

    c=30;//window.c=30

}

fun()

console.log(a)//10

console.log(b)//报错,下面语句不输出;

console.log(c)//前一句注释,30

 参考链接:JS作用域和变量提升

4、JS对象考题

一切事物皆对象,js所有数据类型皆对象,如函数(可添加值)、字符串......

但原型是函数所特有的,我们不能通过.prototype往数组、对象上加东西

var str="123"==>前身==>var str=new String('123')

(str.toString()要不怎么能用toString方法呢)

1. 对象是通过new操作符构建出来的,所以对象之间不相等(除了引用外);
2. 对象注意:引用类型(共同一个地址);
3. 对象的key都是字符串类型;
4. 对象如何找属性|方法;
    查找规则:先在对象本身找 ===> 构造函数中找 ===> 对象原型中找 ===> 构造函数原型中找 ===> 对象上一层原型查找

原型链顶端null,找不到对象属性为undefined

 

对象的key都是字符串类型

var a={}

var b={

    key:'a'

}

var c={

    key:'c'

}

//由于key值是字符串

//b,c是对象,就会有如下转换

//console.log(b.toString())//[object Object]

//console.log(c.toString())//[object Object]

a[b]='123';

a[c]='456';

console.log(a[b])//456

对象找属性|方法 

查找规则:先在对象本身找 ===> 构造函数中找 ===> 对象原型中找 ===> 构造函数原型中找 ===> 对象上一层原型查找

原型链顶端null,找不到对象属性为undefined

用构造函数生成的对象,无法共享属性和方法

//new Array()

 [1,2,3] === [1,2,3]   //false

设计原型,用于共享数据和方法

构造函数.prototype==实例对象.__proto__

构造函数自动生成prototype

JS作用域+this指向+原型的考题

function Foo(){
    getName = function(){console.log(1)} //注意是全局的window.
    return this;
}

Foo.getName = function(){console.log(2)}//静态成员
Foo.prototype.getName = function(){console.log(3)}
var getName = function(){console.log(4)}
function getName(){
    console.log(5)
}

Foo.getName();    //2
getName();           //4
Foo().getName();  //1
getName();          //1
new Foo().getName();//3,//静态成员(后面再加的)只能通过构造函数来访问

5、JS判断变量是不是数组,你能写出哪些方法?

不能用typeof()

1、isArray

Array.isArray(arr)

2、constructor

var arr = [1,2,3];
console.log(  arr.constructor.toString().indexOf('Array') > -1 )//.constructor后是f  Array(),不等于-1,代表能查找到

3、isPrototypeof():判断当前对象是否为另外一个对象的原型

var arr = [1,2,3];
console.log(  Array.prototype.isPrototypeOf(arr) )

4、instanceof 

var arr = [1,2,3];
console.log( arr instanceof Array );

5、原型prototype

var arr = [1,2,3];
console.log( Object.prototype.toString.call(arr).indexOf('Array') > -1 );

6、给字符串新增方法实现相应功能

如:

给字符串对象定义一个addPrefix函数,当传入一个字符串str时,它会返回新的带有指定前缀的字符串,例如:

console.log( 'world'.addPrefix('hello') ) 控制台会输出helloworld

代码:

String.prototype.addPrefix = function(str){
    return str  + this;
}
console.log( 'world'.addPrefix('hello') )

7、js为什么是单线程的?

JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。

JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完 全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

8、js是单线程,怎样执行异步的代码?

1. js是单线程的语言,单线程任务被分为同步任务和异步任务。
2. js代码执行流程:同步执行完==>事件循环
    同步的任务都执行完了,才会执行事件循环的内容
    进入事件循环:请求、定时器、事件....

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。

如:点击事件、setTimeout函数,总不能等点击才继续执行吧

任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务

(1)所有同步任务都在主线程上执行,形成一个执行栈(execution contextstack)。

(2)主线程之外,还存在一个"任务队列"(taskqueue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。

(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

(4)主线程不断重复上面的第三步。

只要主线程空了,就会去读取”任务队列”,这就是JavaScript的运行机制。这个过程会不断重复。

如:

for(var i=0;i<3;i++){

//时间到了,进入任务队列,若是点击事件,就点击了才进

    setTimeout(function(){

        console.log(i);

    },1000*i)

}

输出:

3

3

3

 link:

什么是事件循环?

结合前面解释,......,主线程不断从任务队列中读取事件,这个过程是循环不断的,这种运行机制就叫做Event Loop(事件循环)!

为什么会有事件循环机制?

js的一大特点就是单线程,但是单线程就导致有很多任务需要排队,只有一个任务执行完才能执行后一个任务。如果某个执行时间太长,就容易造成阻塞;为了解决这一问题,JavaScript引入了事件循环机制

9、宏任务与微任务

js中,又将异步任务分为宏任务和微任务

I/O、定时器、事件绑定、ajax等都是宏任务

Promise的then、catch、finally和process的nextTick都是微任务

宏任务与微任务是什么?

宏任务与微任务表示异步任务的两种分类
宏任务:包括整体代码script(可以理解为外层同步代码)、settimeout、setInterval、i/o、ui render
微任务:promise、object.observe、MutationObserver(监听DOM树的变化)
因为异步任务放在队列中,自然而然宏任务与微任务就存放在宏任务队列与微任务队列中
 

代码的执行顺序:先执行同步代码,遇到异步宏任务则将异步宏任务放入宏任务队列中,遇到异步微任务则将异步微任务放入微任务队列中,当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,微任务执行完毕后再将异步宏任务从队列中调入主线程执行,一直循环直至所有任务执行完毕(事件循环EventLoop)。

放入队列时宏任务优先,将队列中的代码拿到执行栈中执行微任务优先

要执行宏任务的前提是清空了所有的微任务

link:

为什么要有微任务?

既然我们知道了微任务与宏任务,但异步任务为什么要区分宏任务与微任务呢,只有宏任务不可以吗?

因为事件队列其实是一个“先进先出”的数据结构,排在前面的事件会优先被主线程读取, 那如果突然来了一个优先级更高的任务,还让去人家排队,就很不理性化, 所以需要引入微任务。

在当前的微任务没有执行完成时,是不会执行下一个宏任务的

输出:

111

微事件1

微事件2

内层宏事件3

 例题:

console.log(1)
setTimeout(function(){
  console.log(2);
  let promise = new Promise(function(resolve, reject) {
      console.log(7);
      resolve()
  }).then(function(){
    console.log(8)
  });
},1000);
setTimeout(function(){
  console.log(10);
  let promise = new Promise(function(resolve, reject) {
      console.log(11);
      resolve()
  }).then(function(){
    console.log(12)
  });
},0);
let promise = new Promise(function(resolve, reject) {
    console.log(3);//new Promise 在实例化过程中所执行的代码都是同步执行的(function中的代码)
    resolve()
}).then(function(){
  console.log(4)
}).then(function(){
  console.log(9)
});
console.log(5)

输出:

1

3

5

4

9

10

11

12

2

7

8

 10、new操作符做了什么

1. 创建了一个空的对象
2. 将空对象的原型,指向于构造函数的原型
3. 将空对象作为构造函数的上下文(改变this指向,执行构造函数里面的代码,如this.name=''xiaixia')
4. 对构造函数有返回值的处理判断(返回新对象)

11、原型链

1. 原型可以解决什么问题
    对象共享属性和共享方法
2. 谁有原型
函数拥有:prototype
对象拥有:__proto__
3. 对象查找属性或者方法的顺序
    先在对象本身查找 --> 构造函数中查找 --> 对象的原型 --> 构造函数的原型中 --> 当前原型的原型中查找
4. 原型链
    4.1 是什么?:就是把原型串联起来
    4.2 原型链的最顶端是null 

 12、什么是闭包?

闭包是指可以访问另一个函数作用域变量的函数,一般是定义在外层函数中的内层函数。

为什么需要闭包?

局部变量无法共享和长久的保存(如在函数执行完后,里面的变量就没用了,就会销毁),而全局变量可能造成变量污染(结束才销毁),所以我们希望有一种机制既可以长久的保存变量又不会造成全局污染。

闭包的缺点
    变量会驻留在内存中,造成内存损耗问题,导致性能可能会下降。
                解决:再不用的时候,把闭包的函数设置为null
     内存泄漏【ie】 ==> 可说可不说,如果说一定要提到ie

什么时候用到?

如:实现请求一次加1

 输出:2 3 4

var lis = document.getElementsByTagName('li');
for(var i=0;i<lis.length;i++){
(function(i){
    lis[i].onclick = function(){
    alert(i);
    }
})(i)
}
//如有3个标签,依次分别弹出 0 1 2

13、js继承有哪些方式

1、构造函数继承

核心原理: 通过 call() 把父类型的 this 指向子类型的 this ,这样就可以实现子类型继承父类型的属性。

  1. 先定义一个父构造函数

  2. 再定义一个子构造函数

  3. 子构造函数继承父构造函数的属性(使用call方法)

 // 1. 父构造函数
 function Father(uname, age) {
   // this 指向父构造函数的对象实例
   this.uname = uname;
   this.age = age;
 }
  // 2 .子构造函数 
function Son(uname, age, score) {
  // this 指向子构造函数的对象实例
  3.使用call方式实现子继承父的属性
  Father.call(this, uname, age);
  this.score = score;
}
var son = new Son('刘德华', 18, 100);
console.log(son);

2、原型继承

核心原理

① 将子类所共享的方法提取出来,让子类的 prototype 原型对象 = new 父类()

② 本质:子类原型对象等于是实例化父类,因为父类实例化之后另外开辟空间,就不会影响原来父类原型对象

③ 将子类的 constructor 从新指向子类的构造函数

参考下代码

3、组合继承(构造+原型)

一般情况下,对象的方法都在构造函数的原型对象中设置,通过构造函数无法继承父类方法。

// 1. 父构造函数
function Father(uname, age) {
  // this 指向父构造函数的对象实例
  this.uname = uname;
  this.age = age;
}
//方法在原型中添加
Father.prototype.money = function() {
  console.log(100000);
 };
 // 2 .子构造函数 
  function Son(uname, age, score) {
      // this 指向子构造函数的对象实例
      Father.call(this, uname, age);
      this.score = score;
  }

//实现子函数继承父函数的方法
// Son.prototype = Father.prototype;  地址指向,这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化
//所以使用该方法,原理见下图
  Son.prototype = new Father();
  // 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数,否则这里就指向了Father
  Son.prototype.constructor = Son;
  // 这个是子构造函数专门的方法,就不会影响其他对象了
  Son.prototype.exam = function() {
    console.log('孩子要考试');

  }
  var son = new Son('刘德华', 18, 100);
  console.log(son);

4、ES6

class Parent{
    constructor(){
        this.age = 18;
    }
}

class Child extends Parent{
    constructor(){
        super();
        this.name = '张三';
    }
}
let o1 = new Child();
console.log( o1,o1.name,o1.age );

14、 说一下call、apply、bind区别

同:

作用相同,都是动态修改this指向;都不会修改原先函数的this指向。

异:

(1)执行方式不同:

        call和apply是改变后页面加载之后就立即执行,是同步代码。

        bind是异步代码,改变后不会立即执行;而是返回一个新的函数。加个()可执行

(2)传参方式不同:

        call和bind传参是一个一个逐一传入,不能使用剩余参数的方式传参。

        apply可以使用数组的方式传入的,只要是数组方式就可以使用剩余参数的方式传入。

   (3)修改this的性质不同:

        call、apply只是临时的修改一次,也就是call和apply方法的那一次;当再次调用原函数的时候,它的指向还是原来的指向。

        bind是永久修改函数this指向,但是它修改的不是原来的函数;而是返回一个修改过后新的函数,此函数的this永远被改变了,绑定了就修改不了。

// call()
function sayHi(adjective){
  console.log("Hello " + this.name + ", You are " + adjective);
}
var obj = {name: "Matt"};
sayHi.call(obj, "awesome")
//输出:Hello Matt, you are awesome


//apply() 
const person = {
    name: 'John'
}
function greet(greeting, message) {
    return `${greeting} ${this.name}. ${message}`;
}
let result = greet.apply(person, ['Hello', 'How are you?']);
console.log(result);
//输出:Hello John. How are you?


//bind()
//与 apply() 和 call() 不同,bind() 不会立即执行函数。相反,它返回一个新版本的函数,其 this 被设置为另一个值。
//先看一个例子:
let person = {
    name: 'John',
    getName: function() {
        console.log(this.name);
    }
};
window.setTimeout(person.getName, 1000);
//打印:undefined
//最后一行等效重写为,便于理解
let func = person.getName;
setTimeout(func, 1000);

//要解决此问题,需要将 getName() 方法绑定到 person 对象
//绑定函数 func 现在将此值设置为 person 对象。当你将这个新绑定函数传递给 setTimeout() 函数时,它知道如何获取此人的姓名。
let func = person.getName.bind(person);
setTimeout(func, 1000);
//打印:John

 15、== 和 === 有什么区别?

== 比较值:会自动转换类型

=== 比较值和类型:先判断左右两边的数据类型,如果数据类型不一致,直接返回 false 之后才会进行两边值的判断

==判断等式两边是否相等的情况:

(1)null和undefined不会进行数据转换,null、undefined和不同类型比较,都是false(null和undefined结果为true)

(2)NaN和任何数据进行比较,都是false(包括NaN和NaN相比较也为false)

(3)布尔值是转换为数字1或0再和其他数据进行比较

(4)若其中一个数据类型为数值,那么另一个数据类型会先转换成数值然后再与之比较

  - 数组会先通过调用toString()转换为字符串后再转换为数值,比如[true]转换为字符串后为"true",然后再转换为数值是NaN,所以[true]==1返回false

  (5)“!” 为逻辑非,在操作非布尔值类型的数据时,会将该数据类型先转换为布尔值后再取反

        以下均返回true:

        ① 非空字符串
        ② 非零数值
        ③ 数组
        ④ 对象
        ⑤ 函数

" "==0 //true

"0"==0 //true

" " !="0" //true

123=="123" //true

null==undefined //true

[]==[]  //false,[]==![]   //true,[]==false——>[]==0——>0==0

"hello"==1 //"hello"转换为数字是NaN

true==2//false

[true]==1//false,[true]=>"true"=>NaN

全等比较(===)两边是否相等的情况:

(1)类型不同,一定不相等

(2)两个同为数值,并且相等,则相等;若其中一个为NaN,一定不相等

(3)两个都为字符串,每个位置的字符都一样,则相等

(4)两个同为true,或是false,则相等

(5)两个值都引用同一个对象或函数,则相等,否则不相等(引用类型地址空间可能不一样)

(6)两个值都为null,或undefined,则相等

(7*)两者同为引用类型时,必须是指向同一个引用地址才相等,否则不相等,仅当它们引用相同对象时返回true(5的补充)

(8)-0 === +0   结果为:true
 

不同数据转换成布尔值的结果:

        console.log(Boolean(''));       //false
        console.log(Boolean({}));       //true
        console.log(Boolean([]));       //true
        console.log(Boolean(null));     //false
        console.log(Boolean(undefined)); //false
        console.log(Boolean(NaN));      //false
        console.log(Boolean(Object));   //true

数字和其他简单数据类型进行比较时,会尝试将其他数据类型转换成数值型再进行比较

        console.log(""==false);     //true
        console.log(parseInt(""));   //NaN
        console.log(Number(""));   //0
        console.log(""==0);         //true

对象和其他简单数据类型进行比较的时候,会尝试使用对象的valueOf()和toString()方法将对象转换为原始值进行比较:

        let obj = {
            name:'leon',
            age:18
        };
        console.log(obj.toString());    //[object Object]
        console.log(obj.valueOf());     //{name: 'leon', age: 18}

16、 localStorage、sessionStorage、cookie的区别

共同点:都是保存在浏览器端,并且是同源的

区别:

1. 数据存放有效期

-  sessionStorage : 仅在当前浏览器窗口关闭之前有效。【关闭浏览器就没了】

-  localStorage   : 始终有效,窗口或者浏览器关闭也一直保存,所以叫持久化存储。

-  只在设置的cookie过期时间之前有效,即使窗口或者浏览器关闭也有效。

2. localStorage、sessionStorage不可以设置过期时间
     cookie 有过期时间,可以设置过期(把时间调整到之前的时间,就过期了)

3. 存储大小的限制
    cookie存储量不能超过4k
    localStorage、sessionStorage不能超过5M

4. Cookie:cookie 数据始终在同源的 http 请求中携带(即使不需要),即 cookie 在浏览器 和服务器间来回传递。而 sessionStorage 和 localStorage 不会自动把数据发给服务

17、JS数组去重

1、 方式一:new set

var arr1 = [1,2,3,2,4,1];
function unique(arr){

      return Array.from(new Set(arr1))
      //或

    //return [...new Set(arr)]
}
console.log(  unique(arr1) );

2、方式二: indexOf

var arr2 = [1,2,3,2,4,1];
function unique( arr ){
    var brr = [];
    for( var i=0;i<arr.length;i++){
        if(  brr.indexOf(arr[i]) == -1 ){
            brr.push( arr[i] );
        }
    }
    return brr;
}
console.log( unique(arr2) );

3、方式三:sort

var arr3 = [1,2,3,2,4,1];
function unique( arr ){
    arr = arr.sort();
    var brr = [];
    for(var i=0;i<arr.length;i++){
        if( arr[i] !== arr[i-1]){
            brr.push( arr[i] );
        }
    }
    return brr;
}
console.log( unique(arr3) );

18、 get 和 post 请求在缓存

get 请求类似于查找的过程,用户获取数据,可以不用每次都与数据库连接,所以可以 使用缓存。

post 不同,post 做的一般是修改和删除的工作,所以必须与数据库交互,所以不能使用 缓存。因此 get 请求适合于请求缓存

19、说说前端中的事件流

HTML 中与 javascript 交互是通过事件驱动来实现的,例如鼠标点击事件 onclick、页面 的滚动事件 onscroll 等等,可以向文档或者文档中的元素添加事件侦听器来预订事件。 想要知道这些事件是在什么时候进行调用的,就需要了解一下“事件流”的概念。

什么是事件流:事件流描述的是从页面中接收事件的顺序,DOM2 级事件流包括下面几个 阶段。

事件捕获阶段

处于目标阶段

事件冒泡阶段

addEventListener:addEventListener 是 DOM2 级事件新增的指定事件处理程序的操作, 这个方法接收 3 个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最 后这个布尔值参数如果是 true,表示在捕获阶段调用事件处理程序;如果是 false,表示 在冒泡阶段调用事件处理程序。

20、说一下事件委托

简介:事件委托指的是,不在事件的发生地(直接 dom)上设置监听函数,而是在其父 元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的触发,通过判 断事件发生元素 DOM 的类型,来做出不同的响应。 举例:最经典的就是 ul 和 li 标签的事件监听,比如我们在添加事件时候,采用事件委 托机制,不会在 li 标签上直接添加,而是在 ul 父元素上添加。 好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,也可以有事件触发 机制。 

21、防抖与节流的区别

  • 防抖 (debounce): 将多次高频操作优化为只在最后一次执行,通常使用的场景是:用户输入,只需再输入完成后做一次输入校验即可。(适合多次事件一次响应)

  • 节流(throttle): 每隔一段时间后执行一次,也就是降低频率,将高频操作优化成低频操作,通常使用场景: 滚动条事件 或者 resize 事件,通常每隔 100~500 ms执行一次即可。(适合大量事件按时间做平均分配触发)

22. Javscript数组的常用方法有哪些?

js数组常用方法

23.Javascript字符串的常用方法有哪些?

js字符串常用方法

24.深拷贝与浅拷贝的区别,如何实现深拷贝?

深拷贝与浅拷贝

25、typeof与instanceof的区别

typeofinstanceof都是判断数据类型的方法,区别如下:

  • typeof会返回一个变量的基本类型,instanceof返回的是一个布尔值

  • instanceof 可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型

  •  typeof 也存在弊端,它虽然可以判断基础数据类型(null 除外),但是引用数据类型中,除了 function 类型以外,其他的也无法判断

可以看到,上述两种方法都有弊端,并不能满足所有场景的需求

如果需要通用检测数据类型,可以采用Object.prototype.toString,调用该方法,统一返回格式“[object Xxx]” 的字符串

typeof 操作符返回一个字符串

//基础类型
typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof null // 'object'
//复杂类型
typeof [] // 'object'
typeof {} // 'object'
typeof console // 'object'
typeof console.log // 'function'

虽然typeof nullobject,但这只是 JavaScript 存在的一个悠久 Bug,不代表null 就是引用数据类型,并且null 本身也不是对象

所以,null 在 typeof 之后返回的是有问题的结果,不能作为判断null的方法。如果你需要在 if 语句中判断是否为 null,直接通过===null来判断就好

同时,可以发现引用类型数据,用typeof来判断的话,除了function会被识别出来之外,其余的都输出object

如果我们想要判断一个变量是否存在,可以使用typeof:(不能使用if(a), 若a未声明,则报错)

if(typeof a != 'undefined'){
    //变量存在
}

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上

//object instanceof constructor
object instanceof constructor

构造函数通过new可以实例对象,instanceof 能判断这个对象是否是之前那个构造函数生成的对象

// 定义构建函数
let Car = function() {}
let benz = new Car()
benz instanceof Car // true
let car = new String('xxx')
car instanceof String // true


let str = 'xxx'
str instanceof String // false

关于instanceof的实现原理,可以参考下面:

function myInstanceof(left, right) {
    // 这里先用typeof来判断基础数据类型,如果是,直接返回false
    if(typeof left !== 'object' || left === null) return false;
    // getProtypeOf是Object对象自带的API,能够拿到参数的原型对象
    let proto = Object.getPrototypeOf(left);
    while(true) {                  
        if(proto === null) return false;
        if(proto === right.prototype) return true;//找到相同原型对象,返回true
        proto = Object.getPrototypeof(proto);
    }
}

也就是顺着原型链去找,直到找到相同的原型对象,返回true,否则为false

Object.prototype.toString

对象的原型对象转化成字符串

所有的数据类型都继承了Object的方法
但在数据类型中又改写了toString的方法

call改写this

let s = 123;
s.toString() //"123"


toString.call(123) //"[object Number]"
Object.prototype.toString({})       // "[object Object]",仅是该类型可而已,其他类型不加call会还是这个值
Object.prototype.toString.call({})  // 同上结果,加上call也ok
Object.prototype.toString.call(1)    // "[object Number]"
Object.prototype.toString.call('1')  // "[object String]"
Object.prototype.toString.call(true)  // "[object Boolean]"
Object.prototype.toString.call(function(){})  // "[object Function]"
Object.prototype.toString.call(null)   //"[object Null]"
Object.prototype.toString.call(undefined) //"[object Undefined]"
Object.prototype.toString.call(/123/g)    //"[object RegExp]"
Object.prototype.toString.call(new Date()) //"[object Date]"
Object.prototype.toString.call([])       //"[object Array]"
Object.prototype.toString.call(document)  //"[object HTMLDocument]"
Object.prototype.toString.call(window)   //"[object Window]"

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值