在Javascript中,万物皆对象,但对象也有区别,大致可以分为两类,即:普通对象 Object 和 函数对象 Function。 一般而言,通过 new Function 产生的对象是函数对象,其他对象都是普通对象。
举例说明:
function f1() {
// todo
}
var f2 = function () {
// todo
};
var f3 = new Function('x', 'console.log(x)');
var o1 = {};
var o2 = new Object();
var o3 = new f1();
console.log(
typeof f1,
typeof f2,
typeof f3,
typeof o1,
typeof o2,
typeof o3
);
//输出 > "function" "function" "function" "object" "object" "object"
f1属于函数的声明,最常见的函数定义方式,f2实际上是一个匿名函数,把这个匿名函数赋值给了f2,属于函数表达式,f3不常见,但也是一种函数对象。
Function 是JS自带的对象,f1,f2 在创建的时候,JS会自动通过 new Function() 的方式来构建这些对象,因此,这三个对象都是通过 new Function() 创建的。
Object 和 Function最大的区别是:每个Function对象都有一个prototype表,而 Object没有。
在 Javascript 中创建对象有两种方式:大括号和使用new表达式,o1和o2的创建恰好对应了这两种方式,重点讲一下o3, 如果用Java和C#的思路来理解的话,o3是f1的实例对象,但是o3和f1是不同类型的对象。
为什么呢? 需要理解一下JS的new和构造函数的原理:
// ===================new 与构造函数 机制=========================
function person(name, age) {
this.name = name;
this.age = age;
}
// 机制1 每一个函数对象都有一个表, prototype;
console.log(person.prototype);
person.prototype.get_age = function() {
return this.age;
}
// 机制2: new 关键字 + 函数(参数)
// step1: 创建了一个新的表 {};
// step2: 调用person函数,并把这个新的表作为this传递给函数,进入person里面以后;
// person函数里面this 就是新建的表;
// step3: 将person构造函数的prototype这个表赋值给新的表(this)的.__proto__
// step4: 返回这个新的表;
// new 函数(参数) 的作用是构建一张新的表, 这个函数作为构造函数的作用是初始化表对象;
var p = new person("xiaoming", 34);
//上面四个步骤的伪代码表示
var p = {} //step1
person.call(p,"xiaoming", 34); //step2
person.__proto__ = person.prototype //step3
//第四步省略
由上面代码中的注释可以看出,new constructor()返回的是一个Object。Object对象也叫表
当我们用new调用一个函数时,这个函数也就成了构造函数,new 操作符返回的结果叫做实例。
下面我们把new一个函数返回的对象叫做实例。
可以先通过下面代码的打印输出,理解一下__proto__ 与prototype:
function Base(){
this.baseNum = 1;
this.fun1= function(){
console.log("Base.fun1")
}
}
Base.prototype.num = 10
Base.prototype.fun1 = function(){
console.log("prototype.fun1")
}
let obj = new Base()
obj.fun1()
obj.__proto__.fun1()
console.log("Base.prototype:",Base.prototype)
console.log("Base.__proto__:",Base.__proto__)
console.log("obj.prototype",obj.prototype)
console.log("obj.__proto__",obj.__proto__)
console.log(obj.__proto__ == Base.prototype)//true
console.log(Base.prototype.__proto__==Object.prototype) //true
console.log(Object.prototype.__proto__) //null
输出结果:
由上面代码和输出结果可以得出:
obj中__proto__保存的是 Base的 prototype,Base的 prototype 中的 __proto__保存的是Object的prototype,而Object.prototype.__proto__ 是 null。
prototype:只是定义了实例的数据类型
每一个函数对象都有一个prototype表,但是函数对象不能访问它的prototype,只能定义它的prototype的数据,prototype定义的属性和方法专门留给函数对象的实例访问的:
function f(){}
f.prototype.foo ="abc";
console.log(f.foo);// undefined
var obj =new f();
console.log(obj.foo);// abc
- 代码中的f.prototype虽然和obj 都是一个Object,但是他们表示的意义完全不同:prototype是一个类型的定义或类的定义,obj 是类型(或类)的实例。
- 类的定义只有一份,类的实例可以有很多个,所有new f()得到的实例,共享同一个prototype,通过实例的__proto__访问。
__proto__:引用函数对象的 prototype
__proto__: 存在于普通对象和函数对象中,它的作用就是引用函数对象的 prototype 对象,JS在通过 new 操作符创建一个对象的时候,通常会把父类的 prototype 赋值给新对象的 __proto__属性。
__proto__原型链搜索:
搜索机制:JS引擎访问obj对象属性时,先查找对象本身是否存在属性,如果不存在,会在obj.__proto__上查找,也就是在f.prototype上查找。
f.prototype也没有的话,会递归调用f.prototype的搜索机制,下面就搜到了Object.prototype,
Object.prototype.__proto__是null,终止了递归搜索。
没有__proto__和prototype 也可以实现继承关系的:
当然ES6之后JS就有了class和extends关键字,不需要这么麻烦:
function Base(){
this.baseNum = 1;
this.funBase = function(){
console.log("funBase")
}
}
function A(){
Base.call(this)
this.aNum = 2
this.funA = function(){
console.log("funA")
}
}
let a_obj = new A()
console.log(a_obj.baseNum)
a_obj.funBase()
function B(){
A.call(this)
this.bNum = 22
this.funB = function(){
console.log("funB")
}
}
//A继承了Base,B继承了A和Base
let b_obj = new B()
b_obj.funB()
b_obj.funA()
b_obj.funBase()
输出:
上面代码中的函数B没有定义prototype,也实现了继承与A和Base
上面代码的成员函数直接定义在this中,实际编码很少这样写。 我们把函数作为类来定义的时候,成员函数一般都定义在prototype。
例如:我们把TypeScript的类定义代码编译成JS代码:
编译好的代码中,类的成员函数定义在 prototype中。