一篇JavaScript技术栈带你了解继承和原型链

file

做者 | Jeskson来源 | 达达前端小酒馆前端

1算法

在学习JavaScript中,咱们知道它是一种灵活的语言,具备面向对象,函数式风格的编程模式,面向对象具备两点要记住,三大特性,六大原则。编程

那么是哪些呢?具体的三大特性指的是什么?请记住三大特性:封装(Encapsulation),继承(Inheritance),多态(Polymorphism)。咱们常说的封装,继承,多态,三大特色。六大原则指:单一职责原则(SRP),开放封闭原则(OCP),里氏替换原则(LSP),依赖倒置原则(DIP),接口分离原则(ISP),最少知识原则(LKP)。网络

继承的了解:数据结构

继承,若是有不少的类,每一个类中的属性或者是方法都不同,也有些属性或者方法都是相同的,因此若是去定义它们,有时候要重复去定义这些相同的属性或者方法。闭包

这就致使了代码重复性,这就致使了继承的出现,继承就是儿子继承老子的基因同样,让一个类“儿子类”继承它们的“父亲”,这样就能够拥有“父亲”的全部具备相同的属性或者是方法了。这样的类咱们称为它叫作“父类”,继承顾名思义就是儿子继承老子,具备老子的属性或者是方法,经过这种的继承方式,让全部的子类均可以访问这些属性或者是方法,而不用每次都在子类中去定义这些属性或者是方法咯,多方便,多快捷,多快好省!app

其实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 前端相关的技术文章、工具资源、精选课程、热点资讯。

意见反馈:若本号内容有作得不到位的地方(好比:涉及版权或其余问题),请及时联系咱们进行整改便可,会在第一时间进行处理。

感谢阅读,原创不易,喜欢就点个赞吧,这是我写做最大的动力。

欢迎关注达达的简书!

这是一个有质量,有态度的博客

博客

相关文章
相关标签/搜索