原型
原型基础概念
JavaScript有很多对象,但是对象与对象之间没有联系,我们需要通过一个机制让这些对象联系起来,实现更加强大的功能,这个机制就是原型
原型的使用方法:
每定义一个函数,就随之有一个对象存在,函数通过prototype属性指向该对象。这个对象称之为原型对象。
示例:
function Person(){}
console.log(Person.prototype);//输出结构为Object
继承
继承的方式:
- 1.使用原型对象实现继承
通过原型继承使一个对象从另一个对象中获取本来没有的属性的方法
示例:
function Person(name){
this.name=name;
}
Person.prototype.sayHello=function(){
console.log("hello"+this.name);
return "";
}
var p1=new Person("k");
var p2=new Person("jok");
console.log(p1.sayHello());
console.log(p2.sayHello());
- 2.替换原型对象实现继承
将构造函数的原型对象替换成另一个对象
示例:
function Person(){}
Person.prototype={
sayHello:function(){
console.log("Hello World");
}
}
var p=new Person();
p.sayHello();
- 3.利用Object.create()实现继承
ES5中新增的一种继承实现方式
示例:
var obj={
sayHello:function(){
console.log("我是一个带有sayHello的对象");
return "";//如果函数没有return会出现一个undefined
}
}
var newObj=Object.create(obj);
console.log(newObj.sayHello());
- 4.混入继承
将一个对象的成员赋值给另一个对象
示例:
//方法一(较简单):
var o1={};
var o2={name:"jok"};
o1.name=o2.name;
console.log(o1.name);
//方法二(较复杂):
function extend(o1,o2){
for(var k in o2){
o1[k]=o2[k];
}
}
var o1={name:"jok"};
var o2={age:16,sex:"男"};
extend(o1,o2);
console.log(o1.age);
静态成员与动态成员
静态成员定义
静态成员是指构造函数直接使用的成员
静态成员和实例成员的区别
静态成员使用构造函数名可以直接访问,但是实例成员必须使用对象来访问
示例:
function Person(){
this.name='jok';
this.age=18;
}//实例成员
Person.addr="China";
Person.msg=function(){
console.log(this.addr);
return "";
}//静态成员
//访问静态成员
console.log(Person.msg());
//访问动态成员
console.log(Person.name);//错误,返回结果为Person,无法直接访问name中的值
var obj=new Person();
console.log(obj.name);
属性搜索原则
当对象访问一个属性时,首先会在当前对象中寻找,如果没有,会在原型对象中查找,如果到最后都没有找到,返回undefined,这就是属性搜索原则
示例:
function Person(){
this.name="jok";
}
Person.prototype.name="jack";
var obj=new Person();
console.log(obj.name);
说明:上述代码输出的结果为jok,如果把this.name="jok";这行赋值代码注释掉,输出结果就变为jack,如果把Person.prototype.name="jack";这行原型赋值的代码也注释掉,则输出undefined
原型链
前言:
- JavaScript没有子类和父类的概念,也没有类和示例的区分,全靠原型链来实现继承
- JavaScript是使用构造函数生成实例对象,这种方式有一个缺点,就是每一个实例对象,都有自己的属性和方法的副本,不仅无法做到数据共享,对资源也是会造成很大的浪费
- 设计者引入了prototype属性,这个属性里包含了一个对象(prototype对象),所有实例对象需要共享的属性和方法,都放在这个对象中,不需要共享的属性和方法放在构造函数中
构造函数,原型对象,实例对象的关系
- 构造函数的prototype属性就是其原型对象
- 原型对象的constructor属性又指回了构造函数
示例:
function Person(){};
console.log(Person.prototype.constructor===Person);//原型对象指回了构造函数,输出为true
- 实例对象原本不具有constructor属性,但是继承自构造函数,所以也就有了,实例对象的construction也指回了构造函数
示例:
function Person(){};
console.log(new Person().constructor===Person);//实例对象指回了构造函数,输出为true
console.log(new Person().constructor.prototype===Person.prototype);//实例对象转化为原型对象
- 函数本质上就是对象,自定义函数和内置构造函数都是Function函数,而Function函数的构造函数就是本身
示例:
function Person(){};
console.log(Person.constructor===Function);//因为自定义函数出自Function,所以输出结果为true
console.log(String.constructor===Function);//因为内置函数也出自Function,所以输出结果也为true
console.log(Function.constructor===Function);//Function构造函数为它本身,所以输出true
原型链定义
对象有原型对象,原型对象又有原型对象,这就形成了一个链式结构,称之为原型链

image.png
prototype和__ proto__
- prototype称之为显式原型,__ proto__称之为隐式原型
- 所有对象中都有一个__proto __属性
- __proto __属性是专门用来查看实例对象的原型对象
- 函数对象(自定义普通函数,构造函数,内置构造函数)同时拥有prototype属性和__proto __属性
- 由于构造函数,原型对象,实例对象之间可以互相转换,形成了一个闭环,所以没办法访问到原型对象的原型对象。所以有了一个隐式原型__proto __,指向了构造函数的原型对象
- 构造函数的原型对象,继承自Object的原型对象,而Object的原型对象是null
示例:
function A(){}
var a=new A();
console.log(a.__proto__);//首先指向的是A的原型对象,也就是A
console.log(a.__proto__.__proto__);//继续往上找一层就是object,所以输出结果为object
console.log(a.__proto__.__proto__.__proto__);//object再往上找就是null,所以输出为null
注意:
- 普通对象没有prototype属性
- 如果用prototype属性来查看实例对象的原型对象可能会出错
- 实例对象.constructor.prototype并不一定访问的是构造函数的原型对象
示例:
function A(){}
function B(){}
A.prototype=new B();
var a=new A();
console.log(a.constructor.prototype===A.prototype);//因为构造函数A的原型指向了B的实例所以返回false,此时A的原型===B的原型
console,log(a.__proto__===A.prototype);//返回true
- 使用__proto __属性访问到的一定是实际构造函数的原型对象