一切都是对象!
以下的四种(undefined, number, string, boolean)属于简单的值类型,不是对象。剩下的几种情况——函数、数组、对象、null、new Number(10)都是对象。他们都是引用类型。
判断一个变量是不是对象非常简单。值类型的类型判断用typeof,引用类型的类型判断用instanceof。
var fn = function () { }; console.log(fn instanceof Object); // true
java中的对象都是new一个class出来的,而且里面有字段、属性、方法,规定的非常严格。但是javascript就比较随意了,数组是对象,函数是对象,对象还是对象。对象里面的一切都是属性,只有属性,没有方法。那么这样方法如何表示呢?方法也是一种属性。因为它的属性表示为键值对的形式。而且javascript中的对象可以任意的扩展属性,没有class的约束。
在javascript中,一切(引用类型)都是对象,对象是属性的集合。
一切对象都是通过函数创建的!
譬如:
var obj = {a: 1, b: 2}, var mm = {12, 'X', false}
实质上的本质为:
var obj = new Object(); obj.a = 1; obj.b = 2; var mm = new Array(); mm[0] = 12; mm[1] = X; mm[2] = false;
其中的Object和Array都是函数。
上面的写法其实是下面写法的一种"便捷方式"。
原型 prototype
!每一个函数都有一个默认属性“prototype”。
前面说过,每一个函数都是一种对象,都是属性的集合,你可以对函数进行自定义属性。Javascrpit默认给每一个函数一个默认属性---prototype。
这个prototype的属性值是一个对象(属性的集合),默认的只有一个叫做constructor的属性,指向这个函数本身。
当然,prototype作为一个对象,属性的集合,也可以自定义的增加许多属性。
function Fn() { } Fn.prototype.name = 'Tom'; Fn.prototype.getYear = function () { return 1988; };
隐式原型 _proto_
!每一个对象都有一个隐藏的属性“_proto_”,这个属性引用了创建这个对象的函数的prototype。即:fn.__proto__ === Fn.prototype.
这里的"__proto__"称之为“隐式原型”。
在上面的这张图中可以看出来,自定义函数Foo.__proto__指向Function.prototype,Object.__proto__指向Function.prototype。Function也是一个函数,函数是一种对象,也有__proto__属性。既然是函数,那么它一定是被Function创建。所以——Function是被自身创建的。所以它的__proto__指向了自身的Prototype。
每个对象都有一个__proto__属性,指向创建该对象的函数的prototype。而每个函数的prototype也是一个对象,他同样有一个_proto_属性,其本质上与var obj = {} 是一样的,都是被Object创建,所以它的__proto__指向的就是Object.prototype。
而Object.prototype的_protype属性比较特殊,它指向的是一个null值。
typeof & instanceof
对于值类型,你可以通过typeof判断,string/number/boolean都很清楚,但是typeof在判断到引用类型的时候,返回值只有object/function,你不知道它到底是一个object对象,还是数组,还是new Number等等,这个时候就需要用到instanceof。
Instanceof运算符的第一个变量是一个对象,暂时称为A;第二个变量一般是一个函数,暂时称为B。
Instanceof的判断队则是:沿着A的__proto__这条线来找,同时沿着B的prototype这条线来找,如果两条线能找到同一个引用,即同一个对象,那么就返回true。如果找到终点还未重合,则返回false。
比如:
console.log(Object instanceof Function); console.log(Function instanceof Object); console.log(Function instanceof Function);
结果都为true。
而如何理解instanceof的作用呢?
其实instanceof表示的就是一种继承关系,或者原型链的结构。
继承 & 原型链
“继承”是常用面向对象语言中最基本的概念,但是java中的继承与javascript中的继承又完全是两回事儿。
javascript中的继承是通过原型链来体现的。
如下面的代码:
function Foo() {}; var f = new Foo();
f.name = 'Cat'; Foo.prototype.name = 'Tom'; Foo.prototype.sex = 'Man' console.log(f.name); console.log(f.sex);
结果:
访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着__proto__这条链向上找,这就是原型链。
在上例中,访问f.sex时,f的基本属性中没有sex,于是沿着__proto__找到了Foo.prototype.sex。
我们在实际应用中如何区分一个属性到底是基本的还是从原型中找到的呢?答案是——hasOwnProperty。譬如: if (f.hasOwnProperty(X)){}。
而问题又出来了,在f中是没有hasOwnProperty这个方法的,那这个方法是从哪里来的呢?
其实他是从Object.prototype中得到的。对象的原型链是沿着__proto__这条线走的,因此在查找f.hasOwnProperty属性时,就会顺着原型链一直查找到Object.prototype。
由于所有的对象的原型链都会找到Object.prototype,因此所有的对象都会有Object.prototype的方法。这就是所谓的“继承”。
ps:
如果继承得到的方法感觉不合适,你可以自己进行修改。
如果继承无法满足需要,还可以自己添加方法。不过如果你要添加内置方法的原型属性,最好做一步判断,如果该属性不存在,则添加。如果本来就存在,就没必要再添加了。