JavaScript即学即用教程[1]-类型系统

基本类型

Number, Boolean, String, null, undefined

什么样的变量是 undefined

  1. 声明了变量却没赋值
  2. 未声明的变量

包装类型

其实js里面也有像java,c#里所谓的包装类型

1
2
var a = 123
var b = a.toString()

请问a既然是一个简单的基本类型,内存栈上的4个字节的数字类型,为什么有toString方法呢?
显然,这里也发生了包装,其过程大概是这样的:
1
2
var tmp = new Number(a)
var b = tmp.toString()

引用类型

JS中一切皆对象,我们可以统称为除了基本类型的这些其他元素,都叫做对象。因为他们都可以有自身的属性、自身的方法。

不过,从面向对象的角度来看,我们还是应该像学习Java那样,对js里的对象进行一下划分。我对js里的类型划分是这样的定义的:

除了基本类型,都是引用类型。引用类型有内置的如Date,Array,Object等,也有自己创建的类型如Person, Dog等等。

然而,js又是一个偏函数式的语言,那么,在js里函数承担什么角色呢。

  1. 它其实既能当做构造函数创建一个类的定义,如Dog
  2. 又能当做一个全局函数,来提供给别人使用。如parseInt。当然,全局其实也可以看做是window实例对象的一个方法:window.parseInt
  3. 函数又能当做一个对象的方法,像其他面向对象语言那样,如arr.slice()
  4. 函数还能作为匿名函数参数传递,像很多函数式语言一样。如arr.map(function (item,index) {})

所以,JavaScript既能搞得来面向对象,又能充分发挥函数的灵活性,岂不美哉。

小帮助:我认为去理解JavaScript类型系统的时候,就按照传统面向对象语言来理解就好,额外再知道JavaScript任何东西都能当做一个普通对象来用就好了。
比如: 我们可以认为 JavaScript 中 Date、Array 这些都是所谓的 class,而你自己创建的 obj, arr 或者系统内置的数学对象Math,这些都是其对应构造函数的实例: obj由Object构造、arr由Array构造、Math由Object构造。
除此之外,JavaScript唯一一个特殊的地方在于: Object、Array 这些class,是由 Function 这个class构造而来。

类型判断

typeof能够返回的结果只有: ‘undefined’, ‘bollean’, ‘number’, ‘string’,’object’,’function’
可见,包含了基本类型的所有类型,除了null,null这种基本类型会被判定为’object’,而其他所有引用类型也会被判定为’object’或’function’.

因此,使用typeof可以用来判定基本类型中的 stirng, number, boolean, undefined

而遇到引用类型,则需要用更严格的方法来判定:

  • 方法1: instanceof 可以判定一个引用类型是否属于某个类型。而所有引用类型其实都继承自Object,所以任何引用类型instanceof Object都是true。但 instanceof 不能严格去获得某个变量的具体类型
  • 方法2: 通过我们通常都学习过的 Object.prototype.toString.call(arr) 这样的一个 toString 函数的 hack 调用,来获取到目标对象自身的某种类型属性。他会返回: [object Array] 这样的字符串,我们可以从中 slice 读取到类型名。
  • 方法3:这是我自己发现的一种方法。不确定是否具有100%的精确性。即: 我们可以访问实例的 constructor 属性获取到其构造函数class. 而构造函数本身通常有一个name属性会返回其自身的函数名(类名).
    如: var Person = function() {}; var p = new Person(); console.log(p.constructor.name);

注意:数组类型在使用 instanceof时 在浏览器端务必要注意, 因为浏览器端 iframe 的存在会导致两个window上下文传递的数组对象无法正确 instanceof。可以改用其他的如 Array.isArray 或 上面提到的方法。

typeof 在什么时候返回 function 什么时候返回 object

这里其实很简单,只要typeof的目标是 Function 这个类的直接实例(即如果其本身是一个构造函数),那么会返回 function。 而如果 typeof 的目标是由构造函数创建而来的实例,那么返回 object。

例如 Array, Date,其实他们都是由 Function 这个创建而来,即他们的 constructor 是 Function。所以他们 typeof 时 返回 function。
而 Math, JSON,你自己创建的obj, arr。他们分别由构造类Object和构造类Array去构造而来,因此他们返回 object。

我的总结

在 JavaScript 中,所有构造函数如Date等,都是 Function 这个构造器的一个实例,但注意这些构造函数并没有跟 Function 建立原型继承关系(即Date函数自身是Function的实例对象),而在类的继承关系上,Date它是跟 Object 建立的原型继承关系(即 Date.prototype.proto ==== Object.prototype)。

Function类型的实例比较特殊,因为Function类型的实例还是一个Function,比如Object、Array、Date都是Function类型的实例。所以JS中,Function的实例,才相当于其他语言当中的类,class

重点来了

由于Array、Date,都是从 Function 实例化而来,所以他们都可访问 Function.prototype 上的东西。
而Array、Date的实例,都是从Array、Date继承而来,因此实例会继承Array或Date的prototype上的东西.
然而,Array的实例却跟Function没有任何关系,因为js中继承是依靠原型继承的,Array.prototype跟Function扯不上关系,那Array的实例也跟Function没有半毛钱关系。如图:

反而, 由于Array、Date这些class的prototype都是继承自Object,所以原型链拐向了Object。因此var arr = new Array()这样一个实例,他顺着原型链是找到了Object.prototype. 如图:

选学内容

如果我们刨根问底,去寻找Object和Function的根源的话,那这个问题又稍微有点复杂了。
我们知道Object类型,是实例化自Function,所以 Object.proto 指向 Function.prototype的,

而Function看起来是从自己的Function.prototype来实例化出来的, 即: Function.__proto__ == Function.prototypeFunction.prototype 却不是一个对象,而又是一个函数, 但奇特的是Function.prototype这个函数并没有prototype属性,只知道该函数继承自Object.prototype(Function.prototype.proto === Object.prototype) (所以这个函数怎么造出来的呢?竟然继承自一个对象,看来是这里揭示了为何js里一切皆对象把,连Function往上找源头都最终找到它继承自Object.prototype)

Array是继承自Object.prototype。而Object.prototype继承自谁呢?答案是Object.prototype是继承自null,

所以,最终的结果如图所示:

再上一张比较全的图:

这张图上少画了一笔,最上方的空函数,其__proto__也应该指向Object.prototype,。

所以,js中所有类型的根源,竟然是Object.prototype顶上的那个null….

而typeof null 又是 ‘object’,所以在js当中,一切皆对象!!!