JavaScript 原型与继承
javascript 原型与继承
javascript 中函数原型是实现继承的基础。prototype、construct、原型链以及基于原型链的继承是面向对象的重要内容
prototype
-
原型即 prototype,是函数的一个属性,是一个对象
function car() {} console.log(typeof car.prototype); console.log(car.prototype); // object // {...}
-
所有被构造函数构造出的对象都能访问 prototype 上定义的属性和方法
function car() { this.brand = "bmw"; this.color = "red"; } car.prototype.price = "100 万"; var car1 = new car(); var car2 = new car(); console.log(car1.price); console.log(car2.price); // 100 万 // 100 万
-
构造函数内部和 prototype 定义了同名属性,实例对象会优先调用构造函数中的属性
function car() { this.price = "10 万"; } car.prototype.price = "100 万"; var car1 = new car(); console.log(car1.price); // 10 万
-
通过实例对象不能更改 prototype 上的属性
function car() {} car.prototype.price = "100 万"; var car1 = new car(); car1.price = "10 万"; console.log(car.prototype.price); // 100 万
一般将不变化的内容或方法放在 prototype 下,需要动态变化的放在构造方法内,通过参数配置
constructor
-
constructor 指向构造函数本身
实例对象的 constructor 属性指向构造函数
function car() {} var car = new car(); console.log(car.constructor); console.log(car) // car(){} // car(){}
-
constructor 可以被更改
constructor 可以被修改,但是并不会影响实例化对象
function bike() { this.name = "bike"; } bike.prototype.name = "bike"; function car() {} car.prototype = { constructor: bike } var car = new car(); console.log(car.prototype); console.log(car.name); // {constructor: bike(){}, ...} // undefined
__proto__
-
构造函数在实例化时,将其 prototype 挂载到函数内 this 的
__proto__
下function car() {} car.prototype.name = "jett"; var car = new car(); console.log(car.prototype); console.log(car.__proto__); // car.prototype -> // { // name: "jett", // construct: car(){} // _proto_: {...} // } // car._proto_ -> // { // name: "jett", // construct: car(){} // _proto_: {...} // } //
可以看到,打印出的 car.prototype 和 car.
__proto__
内容一致。因为在实例化对象时,car.prototype 被挂载到函数内的 this.__proto__
上,即实例对象的__proto__
属性上prototype 是构造函数的属性,
__proto__
属于每个实例对象的,是一个内部属性,它们指向相同的内容 -
可以通过实例对象访问
__proto__
属性,并对其进行修改function car() {} car.prototype.name = 'bwm'; var car = new car(); console.log(car.name); car.__proto__= { name:"benz" } console.log(car.name); // bwm // benz
也可以更改 prototype 的属性到达效果
function car() {} car.prototype.name = 'bwm'; var car = new car(); console.log(car.name); car.prototype.name = 'benz'; console.log(car.name); // bwm // benz
但是,将 prototype 重新赋值并不能对之前实例化的对象造成影响
function car() {} car.prototype.name = 'bwm'; var car = new car(); console.log(car.name); car.prototype = { name: "benz" } console.log(car.name); // bwm // bwm
这是因为重新赋值相当于创建新对象,使 prototype 指向的新的对象,而实例对象的
__proto__
属性依然指向原来的内容,相当于一个对象的两个引用,其中一个不在指向该对象,而且指向了新对象这不能对已经实例化出的对象造成影响,但是后面再实例化对象则可以造成影响,因为实例化过程中将修改后的 prototype 挂载到了实例对象的
__proto__
属性下,二者指向同一对象
原型链
-
prototype 中的
__proto__
属性function car() {} var car = new car(); console.log(car.prototype);
当我们打印构造函数的 prototype 属性时,可以看到
{ constructor: car(), __proto__: {...} }
prototype 中也有
__proto__
属性,实例化过程 protorype 被挂载到实例对象的__proto__
下,这就意味着实例对象的__proto__
中也有一个__proto__
属性因为这里的 prototype 是一个非空对象,是由 new object() 或者其他自定义构造方法实例化出的,自然也有
__proto__
属性 -
链式的
__proto__
原型链是由
__proto__
组成的链接,原型链的顶端是 object.prototypejuniorcoder.prototype.basicskill = "html/css"; function juniorcoder() { this.lowerskill = "javascript" } var junior = new juniorcoder(); seniorcoder.prototype = junior; function seniorcoder() { this.advancedskill = "vue"; } var senior = new seniorcoder(); console.log(senior);
这里将 juniorcoder() 的实例对象赋值给 seniorcoder.prototype,打印出
seniorcoder { advcedskill: "vue", __proto__: { // senior.__proto__ ,即 seniorcoder.protoype lowerskill: "javascript", __proto__: { // junior.__proto__ ,即 juniorcoder.prototype basicskill: "html/css", __proto__: { // object.prototype constructor: object(), tostring: tostring() // ... } } } }
可以看出,senior 的
__proto__
属性指向 juniorcoder() 实例 junior,这是因为之前 将 junior 赋值给了 seniorcoder.prototype此外,junior 的
__proto__
也指向了一个对象,这个对象就是 juniorcoder.porotype,相当于 new object() 得出的,所以 junior 的__proto__
下的__proto__
就是 object.prototype,这就是原型链的顶端,在里面我们还可以看到 tostring 方法等等 -
访问原型链上属性
juniorcoder.prototype.basicskill = "html/css"; juniorcoder.prototype.sex = "man"; function juniorcoder() { this.lowerskill = "javascript" this.age = 22; } var junior = new juniorcoder(); seniorcoder.prototype = junior; function seniorcoder() { this.advancedskill = "vue"; } var senior = new seniorcoder(); console.log(senior.age); console.log(senior.sex); // 22 // man
senior 可以访问 junior 本身的属性,也可以访问 juniorcoder.prototype 上的属性,因为 junior 被挂载到了 seniorcoder.prototype 上
juniorcoder.prototype.basicskill = "html/css"; function juniorcoder() { this.lowerskill = "javascript"; this.years = 3; } var junior = new juniorcoder(); seniorcoder.prototype = junior; function seniorcoder() { this.advancedskill = "vue"; } var senior = new seniorcoder(); senior.years++; // 等同于 senior.years = senior.years + 1; console.log(senior.years); console.log(junior.years); // 4 // 3
可以看到,通过 senior 试图改变 years 属性并不能真正影响 junior 的 years 属性,实际上只是在 senior 下创建了新的 years 属性,并将 junior.years 加一的结果赋值给它
object.creat()
-
object 的 creat 方法用于创建对象,参数指定 prototype,可以为对象或 null
var test = { name: "obj" } var obj = object.create(test); console.log(obj.name); console.log(obj.__proto__ == test); // obj // true
-
object.creat(null)
var obj = object.create(null); console.log(obj); document.write(obj); // {} // 报错
控制台显示 obj 是一个空对象,没有任何属性,包括
__proto__
,如果使用 document.write(obj) 则会报错,因为 document.write 方法会把参数转成字符串再打印在页面,默认调用 tostring() 方法,tostring 方法需要从原型链上继承而来,而 obj 是一个完全的空对象,没有原型链,也没有 tostring 方法,所以会报错
基于原型的继承
-
利用原型链实现继承
juniorcoder.prototype.basicskill = "html/css"; function juniorcoder() { this.lowerskill = "javascript" this.age = 22; } var junior = new juniorcoder(); seniorcoder.prototype = junior; function seniorcoder() { this.advancedskill = "vue"; } var senior = new seniorcoder();
senior 继承了 junior 的自身属性及原型链
-
call/apply 实现继承
function juniorcoder(lowerskill) { this.lowerskill = lowerskill; } function seniorcoder(lowerskill, advancedskill) { juniorcoder.apply(this, [lowerskill]); this.advancedskill = advancedskill; } var senior = new seniorcoder("javascript", "vue");
继承了 juniorcoder 实例的自身属性,不能继承原型链
-
公共原型继承
juniorcoder.prototype.basicskill = "html/css"; function juniorcoder() { this.lowerskill = "javascript" } seniorcoder.prototype = juniorcoder.prototype; function seniorcoder() { this.advancedskill = "vue"; } var senior = new seniorcoder();
senior 继承 juniorcoder 实例的原型链,不继承自身属性,但是改动 seniorcoder.prototype 会影响 juniorcoder.prototype
-
中间对象继承(圣杯模式)
juniorcoder.prototype.basicskill = "html/css"; function juniorcoder() { this.lowerskill = "javascript" } buffer.prototype = juniorcoder.prototype; function buffer() {} seniorcoder.prototype = new buffer(); function seniorcoder() { this.advancedskill = "vue"; } seniorcoder.prototype.basicskill = "markdown"; console.log(seniorcoder.prototype.basicskill); console.log(juniorcoder.prototype.basicskill); // markdown // html/css
继承原型链,不继承自身属性,prototype 不相互影响,这种继承方式更为实用
进行封装以后,更适应企业级开发
juniorcoder.prototype.basicskill = "html/css"; function juniorcoder() { this.lowerskill = "javascript" } function seniorcoder() { this.advancedskill = "vue"; } inherit(seniorcoder, juniorcoder); seniorcoder.prototype.basicskill = "markdown"; console.log(new seniorcoder()); console.log(new juniorcoder()); function inherit(target, origin) { target.prototype = object.create(origin.prototype); target.prototype.constructor = target; target.prototype.superclass = origin; }
使用 object 的 creat 方法直接创建中间对象,将 construtor、superclass 属性设置好,便于分析和维护
hasownproperty()
判断属性是否是实例对象本身的,如果是则返回 true
car.prototype.brand = "bmw"; function car() { this.color = "red"; } var car = new car(); console.log(car.hasownproperty("brand")); console.log(car.hasownproperty("color")); // false // true
instanceof
判断实例对象的原型链上是否有某个构造方法
juniorcoder.prototype.basicskill = "html/css"; function juniorcoder() { this.lowerskill = "javascript" } function seniorcoder() { this.advancedskill = "vue"; } inherit(seniorcoder, juniorcoder); function inherit(target, origin) { target.prototype = object.create(origin.prototype); target.prototype.constructor = target; target.prototype.superclass = origin; } var senior = new seniorcoder(); console.log(senior instanceof seniorcoder); console.log(senior instanceof juniorcoder); console.log(senior instanceof object); // true // true // true
推荐阅读