zoukankan      html  css  js  c++  java
  • JavaScript instanceof深度剖析以及Object.prototype.toString.call()使用

    本文由segementfalt上的一道instanceof题引出:

    var str = new String("hello world");
    console.log(str instanceof String);//true
    console.log(String instanceof Function);//true
    console.log(str instanceof Function);//false
    

    先抛开这道题,我们都知道在JS中typeof可以检测变量的基本数据类型。

    let s = "abcd";
    let b = true;
    let i = 22;
    let u;
    let n = null;
    let o = new Object();
    
    alert(typeof s); //string
    alert(typeof b); //boolean
    alert(typeof i); //number
    alert(typeof u); //undefined
    alert(typeof n); //object
    alert(typeof o); //object
    

    从上面的信息可以看到,如果我们想知道某个值是什么类型的对象时,typeof就无能为力了!ECMAScript引入了instanceof来解决这个问题。instanceof用来判断某个构造函数的prototype是否在要检测对象的原型链上

    function Fn(){};
    var fn = new Fn();
    console.log(fn instanceof Fn) //true
    
    //判断fn是否为Fn的实例,并且是否为其父元素的实例
    function Aoo();
    function Foo();
    Foo.prototype = new Aoo();
      
    let foo = new Foo();
    console.log(foo instanceof Foo);  //true
    console.log(foo instanceof Aoo);  //true
    
    //instanceof 的复杂用法
    
    console.log(Object instanceof Object)      //true
    console.log(Function instanceof Function)  //true
    console.log(Number instanceof Number)      //false
    console.log(Function instaceof Function)   //true
    console.log(Foo instanceof Foo)            //false
    

    看到上面的代码,你大概会有很多疑问吧。有人将ECMAScript-262 edition 3中对instanceof的定义翻译如下:

    function instance_of(L, R) {//L 表示左表达式,R 表示右表达式
    	var O = R.prototype;// 取 R 的显示原型
    	L = L.__proto__;// 取 L 的隐式原型
    	while (true) { 
    		if (L === null) 
     			return false; 
    		if (O === L)// 这里重点:当 O 严格等于 L 时,返回 true 
     			return true; 
    		L = L.__proto__; 
    	} 
    }
    

    我们知道每个对象都有proto([[prototype]])属性,在js代码中用__proto__来表示,它是对象的隐式属性,在实例化的时候,会指向prototype所指的对象;对象是没有prototype属性的,prototype则是属于构造函数的属性。通过proto属性的串联构建了一个对象的原型访问链,起点为一个具体的对象,终点在Object.prototype。

    Object instanceof Object :

    // 区分左侧表达式和右侧表达式
    ObjectL = Object, ObjectR = Object; 
    O = ObjectR.prototype = Object.prototype;
    L = ObjectL.__proto__ = Function.prototype (  Object作为一个构造函数,是一个函数对象,所以他的__proto__指向Function.prototype)
    // 第一次判断
    O != L 
    // 循环查找 L 是否还有 __proto__ 
    L = Function.prototype.__proto__ = Object.prototype  (  Function.prototype是一个对象,同样是一个方法,方法是函数,所以它必须有自己的构造函数也就是Object)
    // 第二次判断
    O == L 
    // 返回 true
    

    Foo instanceof Foo :

    FooL = Foo, FooR = Foo; 
    // 下面根据规范逐步推演
    O = FooR.prototype = Foo.prototype 
    L = FooL.__proto__ = Function.prototype 
    // 第一次判断
    O != L 
    // 循环再次查找 L 是否还有 __proto__ 
    L = Function.prototype.__proto__ = Object.prototype 
    // 第二次判断
    O != L 
    // 再次循环查找 L 是否还有 __proto__ 
    L = Object.prototype.__proto__ = null 
    // 第三次判断
    L == null 
    // 返回 false
    

    现在我们回到开始的题目。
    对于第一条判断:

    console.log(str.__proto__ === String.prototype); //true
    console.log(str instanceof String);//true
    

    第二条:

    console.log(String.__proto__ === Function.prototype) //true
    console.log(String instanceof Function);//true
    

    第三条:

    console.log(str__proto__ === String.prototype)//true
    console.log(str__proto__.__proto__. === Function.prototype) //true
    console.log(str__proto__.__proto__.__proto__ === Object.prototype) //true
    console.log(str__proto__.__proto__.__proto__.__proto__ === null) //true
    console.log(str instanceof Function);//false
    

    总结以上:

    str的原型链:

    str ---> String.prototype ---> Object.prototype
    

    String原型链:

    String ---> Function.prototype ---> Object.prototype
    

    由上面的讨论我们可以得知对于基本类型的判断我们可以使用typeof,而对于引用类型判断要用到instanceof,instanceof无法对原始类型进行判断。当然用constructor也可以来判断数据类型,但是要注意的是其值是可以被人为修改的,我们用另外一道面试题来谈论constuctor:

    var A = function() {}; 
    A.prototype = {}; 
     
    var B = {}; 
    console.log(A.constructor);//Function 
    console.log(B.constructor);//Object 
     
    var a = new A(); 
    A.prototype = {}; 
     
    var b = new A(); 
    b.constructor = A.constructor; 
    console.log(a.constructor == A);//false 
    console.log(a.constructor == b.constructor);//false 
    console.log(a instanceof A);//false 
    console.log(b instanceof A);//true 
    

    在JS高程设计里面,作者是这样写的:无论什么时候,只要创建一个新函数,就会为该函数创建一个prototype属性,这个属性指向函数原型对象。在默认情况下,所有的原型都会自动获得一个constructor属性,这个属性指向prototype所在函数的指针。

    我们先来看下面这个例子:

    function Foo() {
    // ...
    }
    Foo.prototype.constructor === Foo; // true
    var a = new Foo();
    a.constructor === Foo; // true
    

    a本身并没有constructor属性,虽然a.constructor确实指向Foo,但是这个属性并不是a由Foo“构造”的。实际上,.constructor 引用同样被委托给了Foo.prototype,而Foo.prototype.constructor 默认指向Foo。思考以下代码便知:

    function Foo() { /* .. */ }
    Foo.prototype = { /* .. */ }; // 创建一个新原型对象
    var a1 = new Foo();
    a1.constructor === Foo; // false!
    a1.constructor === Object; // true!
    

    我们回到抛出的第二道面试题:

    var A = function() {};  
    A.prototype = {};  //此时对构造函数对象A的prototype属性重新复制,constructor属性不见了
     
    var B = {}; 
    console.log(A.constructor);//Function 函数的构造函数为 function Function() 
    console.log(B.constructor);//Object 普通object的构造函数为 function Object() 
     
    var a = new A();       new一个新对象
    A.prototype = {};      
     
    var b = new A(); 
    b.constructor = A.constructor; 
    console.log(a.constructor == A);//false  a.constructor 为 Object 
    console.log(a.constructor == b.constructor);//false b.constructor 为 Function
    console.log(a instanceof A);//false 
    console.log(b instanceof A);//true 
    

    上面的三种方式都有弊端,接下来引出我们的Object.prototype.toString.call()

    Object.prototype.toString()可以通用的来判断原始数据类型和引用数据类型。

    console.log(Object.prototype.toString.call(123)) //[object Number]
    console.log(Object.prototype.toString.call('123')) //[object String]
    console.log(Object.prototype.toString.call(undefined)) //[object Undefined]
    console.log(Object.prototype.toString.call(true)) //[object Boolean]
    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]
    

    参考:

    1: JS高程设计 第六章
    2: 你不知道的JavaScript(上卷) 第五章
    3: https://www.ibm.com/developerworks/cn/web/1306_jiangjj_jsinstanceof/
    4: https://segmentfault.com/q/1010000003872816?_ea=403162
    5: http://mobile.51cto.com/web-487689.htm
  • 相关阅读:
    【转载】Unity的内存管理与性能优化
    [转载]有关placement new
    国内外有用的课程资源
    os模块批量重命名多个工作簿
    利用xlwings将一个工作表,拆成多个工作簿
    利用xlwings批量打开同一文件夹下的N多EXCEL表格
    回归初心,探索真我写在2022年的开始
    利用xlwings在多个工作簿中批量新增工作表
    今日份试题,关于Python办公自动化应用
    随想
  • 原文地址:https://www.cnblogs.com/ylweb/p/7499953.html
Copyright © 2011-2022 走看看