一篇JavaScript技术栈带你了解继承和原型链
作者 | Jeskson
来源 | 达达前端小酒馆
1
在学习JavaScript
中,我们知道它是一种灵活的语言,具有面向对象,函数式风格的编程模式,面向对象具有两点要记住,三大特性,六大原则。
那么是哪些呢?具体的三大特性指的是什么?请记住三大特性:封装(Encapsulation
),继承(Inheritance
),多态(Polymorphism
)。我们常说的封装,继承,多态,三大特点。六大原则指:单一职责原则(SRP
),开放封闭原则(OCP
),里氏替换原则(LSP
),依赖倒置原则(DIP
),接口分离原则(ISP
),最少知识原则(LKP
)。
继承的了解:
继承,如果有很多的类,每个类中的属性或者是方法都不一样,也有些属性或者方法都是相同的,所以如果去定义它们,有时候要重复去定义这些相同的属性或者方法。
这就导致了代码重复性,这就导致了继承的出现,继承就是儿子继承老子的基因一样,让一个类“儿子类”继承它们的“父亲”,这样就可以拥有“父亲”的所有具有相同的属性或者是方法了。这样的类我们称为它叫做“父类”,继承顾名思义就是儿子继承老子,具有老子的属性或者是方法,通过这种的继承方式,让所有的子类都可以访问这些属性或者是方法,而不用每次都在子类中去定义这些属性或者是方法咯,多方便,多快捷,多快好省!
其实JavaScript
并不是什么强面向对象语言,因为它的灵活性决定了并不是所有面向对象的特征都适合JavaScript
的开发。
我们其实讲到了类,那么类又是怎么理解的呢?
类是什么呢?类是具有属性或者是方法的集合,可以通过类的构造函数创建一个实例的对象。说人话就是,如把人类看做一个类,而我们每一个人就是一个实例的对象,类的实例对象包含两方面:
类的所有非静态(属性或者是方法)
类的所有静态(属性或者是方法)
非静态(属性或者是方法)就是每一个实例的特有的,属于个性。
所有静态(属性或者是方法)就是每一个实例的共性的,属于共性。
说人话就是,个性(非静态)就是每个人的名字都是不相同的,而名字这个属性就是非静态属性;共性(静态)就是每个人都是要吃饭的,而吃饭这个方法就是静态方法。
2
那么在JavaScript
中的类是如何实现的呢?
类的实现:
利用函数创建类,利用new
关键字就可以生成实例对象;利用构造函数实现非静态(属性或者是方法),利用prototype
实现静态(属性或者是方法)。
// 创建函数
function dashucoding() {
console.log('dashucoding')
}
// 函数赋值
var da = dashucoding() // undefined
// 实例对象
var jeskson = new dashucoding() // {}
其中dashucoding
是一个普通函数,也是一个类的构造函数,当调用dashucoding()
的时候,它作为一个普通函数会被执行,会输出dashucoding
,因没有返回值,就会返回undefined
;而当调用new dashucoding()
时,会输出dashucoding
并且返回一个对象。
我们把dashucoding
这个函数来构造对象,所以我们把这个dashucoding
看作构造函数。构造对象,构造函数。即通过利用函数,定义构造函数,就相当于定义一个类,通过new关键字,生成一个实例对象。
// 构造函数
function dashucoding(name) {
this.name = name
}
var da1 = new dashucoding('jeskson');
var da2 = new dashucoding('jeckson');
console.log(da1.name) // jeskson
console.log(da2.name) // jeckson
其中dashucoding
构造函数中多个参数,函数体中多this.name=name
,这句中的this
指向new
关键字返回的实例化对象。
根据构造函数中参数不同,生成的对象中具有的属性name
值也是不同的,这里的name
是什么呢?看的出来吗?就是这个类的非静态(属性或者方法)。
那么如何利用prototype
来实现静态呢?(原型链的知识点)
3
原型对象链,原型链,JavaScript
内建的继承方法被称为原型对象链,又称为原型对象继承。有这样一句话,对于一个对象,因为它继承了它的原型对象的属性,所以它可以访问到这些属性,而原型对象也是一个对象,同理,它也可以有自己的原型对象,所以也是可以继承它的原型对象的属性。
what
?一脸懵逼,是不是没听懂,我觉得如小白,鬼听得懂。原型继承链概念,对象继承其原型对象,而原型对象继承它的原型对象。这概念说得鬼听得懂哦,what?what?what?
赏你一大嘴巴子,你妈妈买菜必涨价,超级加倍。你爷爷下象棋,必备指指点点。
原型链:prototype
?类的prototype
是什么?对象的proto
是什么?
类中的prototype
被称作原型:在JavaScript
中,每当我们定义一个构造函数时,JavaScript
引擎中就会自动为这个类添加一个prototype
。
在JavaScript
中,当我们使用new
来创建一个对象的时候,JavaScript
引擎就会自动为这个对象添加一个__proto__
属性,并指向其类的prototype
。
// 构造函数
function dashucoding(name) {
// 非静态属性
this.name = name;
}
// 每当我们定义一个构造函数,JavaScript引擎就会自动为这个
// 类中添加一个prototype
console.log(dashucoding.prototype)
var da1 = new dashucoding('jeskson');
var da2 = new dashucoding('jeckson');
// 对象的proto
console.log(da1.__proto__);
console.log(da2.__proto__);
console.log(dashucoding.prototype === da1.__proto__);
// true
console.log(dashucoding.prototype === da2.__proto__);
// true
其中dashucoding.prototype是一个对象,dashucoding类的实例化对象da1,da2都有一个属性__proto__
,也是对象。并dashucoding.prototype等于da1或者da2的__proto__
。
在JavaScript中引用类型的相等意味着它们所指向的都是同一个对象,任何一个实例化对象的__proto__
属性都指向其类的prototype。
对象中的__proto__
属性:
// 对象赋值
var pro = {
name: 'jeskson';
}
var person = {
__proto__: pro
}
console.log(person.name) // jeskson
person.name = 'jeckson'
console.log(person.name) // jeckson
看见没,其中的person并没有定义name属性,而console.log出来的结果是jeskson哦,这是为啥呢?这就是所谓JavaScript中最厉害牛逼的原型链结果。
其实我们回去看代码就知道是有关联的关系的,person中属性__proto__
的值为pro,其中的pro指向pro这个对象,pro中的属性具有name:'jeskson'的,也有__proto__
属性,值为Object,而Object指向Object,Object的属性也是有__proto__
属性,其值为null。
来,接下来,让我们更加懂的说一下情况,当我们访问person.name是,其中的过程是什么样的?
当我们person.name进行访问的时候,可以看到我们并没有写name这个属性,首先,person.name会去找对象中是否有这个name属性,如果没有,它就会去找__proto__
属性对象。看到没,在person中是有这个__proto__
属性的,别说没有?
没有,你就是没仔细阅读文章,没有,你就是没看文章内容,没有,你就是不适合。
person中__proto__
属性对象的值是pro对象,所以person的__proto__
指向了pro这个对象,那么就会发现在pro这个对象中具有name这个属性,那么就可以返回其中的值为'jeskson'了。
但是假如我们给person加上了这个name属性的,先看代码我们是不是给它加了name值,这时候我们console.log中的person.name值就不会找__proto__
这个属性了,会去先找其中的name属性,值为'jeckson',所以打印返回的'jeckson'。
这里重点说一个:pro的__proto__
指向是Object,每个对象中都有__proto__
属性,这个属性指向创建出来的对象它们默认是Object类的对象,所以记住对象的属性__proto__
自然指向Object.prototype。
好了好了,那么读懂了原型链,就来说上面没说的,运用prototype实现静态(属性或者是方法)。
// 运用prototype实现静态(属性或者方法)
// 构造函数
function dashucoding(name) {
this.name = name;
}
// 利用prototype实现静态(属性或者是方法)
// 创建了方法
dashucoding.prototype.eat = function() {
console.log('i eat');
}
// 实例对象
var da1 = new dashucoding('jeskson');
var da2 = new dashucoding('jeckson');
// 给这个人添加方法
da1.eat() // i eat
da2.eat() // i eat
console.log(da1.eat === da2.eat) // true
其中一句:dashucoding.prototype.eat = function(){...}
,通过dashucoding实例化的对象__proto__
都会指向dashucoding.prototype。
原型链的知识点,只要构造函数中没有定义同名的非静态(属性或者是方法),那么每个对象进行访问的时候都是访问其内部找到的eat方法,这样我们就运用原型链,实现了类的静态(属性或者是方法)。
// 构造函数
function dashucoding(name) {
this.name = name;
eat() {
console.log('i eat');
}
}
// 这就是同名
4
对象的继承,使用对象字面量创建对象时,会隐式的指向Object.prototype
为新对象的[[Prototype]]
,使用Object.create()
方法创建对象时,会显示指定新对象的[[Prototype]]
。Object.create()
方法接受两个参数,第一个参数为新对象的[[Prototype]]
,第二个参数描述了新对象的属性。
又是懵逼了!!!
// 对象字面量形式
var da = {
name: 'jeskson'
}
// 原型被隐式地设置为Object.prototype形式了,这就懂了
// Object.create()创建,显示指定了Object.prototype
var dada = Object.create(Object.prototype, {
dashucoding: {
id: '123',
code: 'dashucoding',
value: '前端'
}
})
请把以上代码记住,牢牢记住。
实现对象的继承:
var da = {
// 属性
name: 'jeskson',
// 方法
write: function() {
console.log(this.name);
}
}
var dada = Object.create(da, {
name: {value: 'jeckson' }
})
da.write(); // 'jeskson'
dada.write(); // 'jeckson'
console.log(da.hasOwnProperty('write')); // true
console.log(da.isPrototypeOf(dada)) // true
console.log(dada.hasOwnProperty('write') // false
console.log('write' in dada); // true
console.log(dada.__proto__ === da); // true
console.log(dada.__proto__ === Object.prototype) // true
原型链继承:
原型链是JavaScript实现继承的主要方法,其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。实现原型链的基本模式是,让当前构造函数的原型对象等于另一个构造函数的实例。
function A(name, age) {
this.name = name;
this.age = age;
this.family = ["爸爸","妈妈"];
if(typeof(this.getName) != "function") {
A.prototype.getName = function() {
return this.name;
}
}
}
function B() {
this.job = 'IT';
if(typeof(this.getJob) != "function") {
B.prototype.getJob = function() {
return this.job;
}
}
}
B.prototype = new A("jeskson", 12);
var da = new B();
// 实例da 的属性name,age是继承自原型B。prototype
console.log(da.name); // jeskson
console.log(da.age); // 12
console.log(da.hasOwnProperty("name")); // false
console.log(da.hasOwnProperty("age")); // false
// 实例da的原型B.prototype被重写,所以da的构造函数指向A
console.log(da.constructor == B);
console.log(da.constructor == A);
// 输出 false, true
// 一个完整的原型链
// da.__proto__ > B.prototype.__proto__ > A.prototype.__proto__ > Object.prototype
console.log(B.prototype.isPrototype(da));
// true
cosole.log(A.prototype.isPrototype(B.prototype));
// true
console.log(Object.protoype.isProtypeOf(A.prototype));
// true
原型链继承:
JavaScript中的对象继承是构造函数基础的基础,几乎所有的函数都有prototype
属性,除了通过Function.prototype.bind
方法构造出来的函数是个例外,它是可以被替换和修改的。
原型链实现继承,让子类继承父类的静态(属性或者是方法)
// 父类
function Father() {}
Father.prototype.say = function() {
console.log('father')
}
function Son() {}
var son1 = new Son();
console.log(son1.say); // undefined
// 原型链实现继承的关键代码
Son.prototype = new Father();
var son2 = new Son();
console.log(son2.say) // function(){...}
当我们使用Son.prototype = new Father()后,通过new Sow()生成的对象都会有__proto__
属性,这个属性指向Son.prototype
。实现了子类继承了父类的静态(属性或者是方法)。
JavaScript中的原型和原型链:
prototype,当我们创建的每一个函数都有一个prototype
原型属性,这个属性就是一个指针,指向了一个对象,而这个对象的用途就是可以由特定类型的所有实例共享的属性和方法。使用原型的好处就是可以让所有的对象实例共享原型对象所包含的属性和方法。
function da(){}
da.prototype.name = 'jeskson';
da.prototype.age = 12;
da.prototype.job = 'it';
da.prototype.sayName = function() {
alert(this.name);
}
var person1 = new da();
person1.sayName(); // jeskson
var person2 = new da();
person2.sayName(); // jeskson
alert(person1.sayName() === person2.sayName());
// true
da.prototype指向原型对象,da.prototype.constructor
指向da
,默认创建一个新函数,它的原型对象只包含constructor
属性,da对象的实例的内部属性仅仅指向da.prototype
5
__proto__
,所有的对象都具有__proto__
属性,隐式原型,指向构造该对象的构造函数的原型对象。
function da() {}
da.prototype.name = 'jeskson';
da.prototype.age = 12;
da.prototype.job = 'it';
da.prototype.sayName = function() {
alert(this.name);
}
// 实例化对象
var person1 = new da();
console.log(da);
// da(){}
console.log(da.prototype);
console.log(da.prototype.__proto__);
console.log(da.prototype.constructor);
console.log(person1);
console.log(person1.__proto__);
原型链:当为对象实例添加一个属性的时候,这个属性会屏蔽掉原型对象中的同名对象。
function da() {}
da.prototype.name = 'jeskson';
da.prototype.age = 12;
da.prototype.job = 'it';
da.prototype.sayName=function() {
alert(this.name);
}
var person1 = new da();
console.log(person1.name); // jeskon
person1.name="jeckson"
console.log(person1.name); // jeckson
person1.name = null
console.log(person1.name); // null
delete person1.name // 删除,实例属性,注意实例属性
console.log(person1.name); // jeskson
构造函数有一个prototype
属性,指向的是实例对象的原型对象,原型对象有一个constructor
属性,指向的是原型对象对应的构造函数,实例对象有一个__proto__
属性,指向的是该实例对象对应的原型对象。
原型方法:
isPrototypeOf()方法用来判断,某个prototype对象和某个实例之间的关系:
alert(Cat.prototype.isPrototypeOf(cat1)); //true
alert(Cat.prototype.isPrototypeOf(cat2)); //true
hasOwnProperty()方法,每个实例对象都有一个hasOwnProperty()方法,用来判断某一个属性到底是本地属性,还是继承prototype对象的属性。
alert(cat1.hasOwnProperty("name")); // true
alert(cat1.hasOwnProperty("type")); // false
in运算符,用来判断某个实例是否含有某个属性,不管是不是本地属性。
alert("name" in cat1); // true
alert("type" in cat1); // true
构造函数,原型,实例之间的关系:
构造函数,是创建对象的一种常用的方式,其他创建对象的方式还包括工厂模式,原型模式,对象字面量等,我们来看一个简单的构造函数。
// 构造函数
function Da(name, age) {
// 构造函数的命名约定第一个字母使用大写的形式
this.name = name;
this.age = age;
}
每一个构造函数都有一个prototype属性,Da.prototype属性其实是一个指针,指向一个对象,该对象拥有一个constructor
属性,所以构造函数中的prototype属性指向一个对象。
原型:
无论什么时候,只要创建一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象,在默认情况下,所有原型对象都会自动获得一个constructor()
构造函数,这个属性包含一个指向prototype
属性所在函数的指针。
一脸懵逼中!!!
构造函数与原型的关系
构造函数中有prototype属性,指向原型对象中constructor,原型对象中有constructor()构造函数,在原型对象中这个constructor指向构造函数中所在指针。
原型:构造函数的prototype属性所指向的对象。(原型对象)
原型这个对象中,有一个constructor属性又指回构造函数本身。
function Da(name,age) {
this.name = name;
this.age = age;
}
var da = new Da('jeskson', '12');
通过构造函数创建对象的过程叫做实例化,创建出来的对象叫做实例
为原型添加一个方法:
Da.prototype.eat = function() {
console.log('i eat');
}
在实例中调用该方法:
var da = new Da('jeskson', '12');
da.eat(); // i eat
原型中的属性和方法,在连接到其对应的构造函数的实例上,是可以使用的。
构造函数,实例,原型 的关系
构造函数里有什么?
构造函数里有prototype
原型里有什么?
原型里有constructor,eat()方法
实例里有什么?
实例有属性或者是方法
最好的实例代码:
// 创建 Da1 构造函数
function Da1 () {
this.name = 'jeskson';
}
// 给Da1构造函数的原型添加方法
Da1.prototype.eat1 = function() {
console.log('i eat da1');
}
// 创建Da2构造函数
function Da2 () {
this.name = 'jeckson';
}
// 将Da1的实例对象直接赋值给D2的原型
Da2.prototype = new Da1();
// 给Da2的原型添加一个方法
Da2.prototype.eat2 = function() {
console.log('i eat da2');
}
// 实例化Da2
var da2 = new Da2();
da2.eat1(); // jeskson
6
原型链继承,函数声明创建函数时,函数的prototype属性被自动设置为一个继承自Object.prototype的对象,该对象有个自己的属性constructor,其值就是函数本身。
// 构造函数
function Da() {}
// JavaScript引擎
Da.prototype = Object.create(Object.prototype, {
constructor: {
name: true,
age: true
}
});
console.log(Da.prototype.__proto__ === Object.prototype);
// true
创建出来的构造函数都继承自Object.prototype,JavaScript引擎帮你把构造函数的prototype属性设置为一个继承自Object.prototype的对象。
构造函数实现继承,让子类继承了父类的非静态(属性或者是方法)
// 构造函数
function Da(name) {
this.name = name
}
function Son() {
Da.apply(this, agruments)
this.sing = function() {
console.log(this.name);
}
}
var obj1 = new Son('jeskson');
var obj2 = new Son('jeckson');
obj1.sing(); // jeskson
obj2.sing(); // jeckson
组合方式实现继承,原型链继承和构造函数基础,实现对父类的静态及其非静态的(属性或者是方法)的继承。
function Father(name) {
this.name = name
}
Father.prototype.sayName = function() {
console.log(this.name);
}
function Son() {
Father.apply(this, arguments)
}
Son.prototype = new Father();
var son1 = new Son('jeskson');
var son2 = new Son('jeckson');
son1.sayName(); // jeskson
son2.sayName(); // jeckson
寄生组合方式实现继承:Super函数,让Father的原型寄生在Super的原型上,让Son去继承Super,然后把这个过程放到一个闭包内。
// 构造函数
function Father(name) {
this.name = name
}
Father.prototype.sayName = function() {
console.log(this.name);
}
function Son() {
Father.apply(this,arguments)
}
(function() {
function Super(){}
Super.prototype = Father.prototype
Son.prototype = new Super()
}())
var son1 = new Son('jeskson');
子类型构造函数的内部调用父类构造函数
function getArea() {
return this.length * this.width
}
/* 四边形 */
function Rectangle(length, width) {
this.length = length
this.width = width
}
/* 获取面积 */
Rectangle.prototype.getArea = getArea
/* 获取尺寸信息 */
Rectangle.prototype.getSize = function() {
console.log(`Rectangle: ${ this.length }x${ this.width },面积: ${ this.getArea() }`)
}
/* 正方形 */
function Square(size) {
Rectangle.call(this, size, size)
this.getArea = getArea
this.getSize = function() {
console.log(`Square: ${ this.length }x${ this.width },面积: ${ this.getArea() }`)
}
}
var rect = new Rectangle(5, 10)
var squa = new Square(6)
rect.getSize() // Rectangle: 5x10,面积: 50
squa.getSize() // Square: 6x6,面积: 36
7
面向对象,创建对象,使用构造函数创建
var obj = new Object();
字面量创建:
var obj = {};
工厂模式:
var p1 = new Object();
p1.name = 'da';
p1.age = '12'
p1.showName = function() {
return this.name
}
var p2 = new Object();
p2.name = 'da2';
p2.age = '23',
p2.showName = function() {
return this.name
}
采用工厂模式,抽象创建对象的过程,封装相同的属性或者是方法
function createF(name, age) {
var obj = new Object();
obj.name = name;
obj.age = age;
obj.showName = function() {
return this.name;
};
return obj;
}
var p1 = createF('jeskson',12);
var p2 = createF('jeckson',23);
构造模式:
function Person(name, age) {
this.name = name;
this.age = age;
this.showName = function() {
console.log(this.name);
}
}
var p1 = new Person('张三', '1');
var p2 = new Person('李四', '2');
什么是原型链:
在JavaScript中继承的主要方法就是通过原型链,主要是一个原型对象等于另一个类型的实例,由于实例内部含有一个指向构造函数的指针,相当于重写了该原型对象,此时该原型对象包含了一个指向另一个原型的指针。原型链的底层是:Object.prototype.__proto__
,值为null。
JavaScript只有一种结构就是对象,每个实例对象都有一个私有的属性为__proto__
,它的指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象__proto__
,层层向上直到一个对象的原型对象为null。
基于原型链的继承,JavaScript对象有一个指向一个原型对象的链,Object.prototype属性表示Object的原型对象。
let f = function() {
this.a = 1;
this.b = 2;
}
//
function f() {
this.a = 1;
this.b = 2;
}
//
let o = new f(); // {a:1,b:2}
f.prototype.b=3;
f.prototype.c=4;
当继承的函数被调用时,this指向的是当前继承的对象,而不是继承的函数所在的原型对象。
var o = {
a: 2,
m: function() {
return this.a+1;
}
};
console.log(o.m()); // 3
当调用o.m时,'this'指向了o
var p = Object.create(o);
// p是一个继承自o的对象
p.a = 4;
console.log(p.m());
function doSomething(){}
doSomething.prototype.foo = "bar";
console.log( doSomething.prototype );
使用Object.create创建对象
var a = {a: 1};
// a ---> Object.prototype ---> null
var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (继承而来)
var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null
var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty);
// undefined, 因为d没有继承Object.prototype
关于目前文章内容即涉及前端,PHP知识点,如果有兴趣即可关注,很荣幸,能被您发现,真是慧眼识英!也感谢您的关注,在未来的日子里,希望能够一直默默的支持我,我也会努力写出更多优秀的作品。我们一起成长,从零基础学编程,将 Web前端领域、数据结构与算法、网络原理等通俗易懂的呈现给小伙伴。分享 Web 前端相关的技术文章、工具资源、精选课程、热点资讯。
意见反馈:
若本号内容有做得不到位的地方(比如:涉及版权或其他问题),请及时联系我们进行整改即可,会在第一时间进行处理。
感谢阅读,原创不易,喜欢就点个赞吧,这是我写作最大的动力。
欢迎关注达达的简书!
这是一个有质量,有态度的博客