Js世袭总结

2020-01-18 作者:网站首页   |   浏览(75)

时间: 2019-08-28阅读: 137标签: 继承原型链

对于JavaScript的继承和原型链,虽然之前自己看了书也听了session,但还是一直觉得云里雾里,不禁感叹JavaScript真是一门神奇的语言。这次经过Sponsor的一对一辅导和自己回来后反复思考,总算觉得把其中的精妙领悟一二了。

当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。如果让原型对象指向另一个类型的实例.....有趣的事情便发生了.即: Person.prototype = animal2鉴于上述游戏规则生效,如果试图引用Person构造的实例person1的某个属性:1).首先会在instance1内部属性中找一遍;2).接着会在instance1.__proto__(constructor1.prototype)中找一遍,而constructor1.prototype 实际上是animal2, 也就是说在animal2中寻找该属性p1;3).如果animal2中还是没有,此时程序不会灰心,它会继续在animal2.__proto__(Animal.prototype)中寻找...直至Object的原型对象

 

搜索轨迹: person1-- animal2-- Animal.prototype--Object.prototype

 

这种搜索的轨迹,形似一条长链, 又因prototype在这个游戏规则中充当链接的作用,于是我们把这种实例与原型的链条称作原型链 .

 

JavaScript继承继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。——《你不知道的JavaScript》基于原型链

  1. JavaScript创建对象

不同于其它大部分语言,JavaScript是基于原型的对象系统,而不是基于类。基于原型的面向对象设计方法总共有三种。

 

1.拼接继承: 是直接从一个对象拷贝属性到另一个对象的模式。被拷贝的原型通常被称为mixins。ES6为这个模式提供了一个方便的工具Object.assign()。在ES6之前,一般使用Underscore/Lodash提供的.extend(),或者 jQuery 中的$.extend(), 来实现。上面那个对象组合的例子,采用的就是拼接继承的方式。

在面向对象语言中,通常通过定义类然后再进行实例化来创建多个具有相同属性和方法的对象。但是在JavaScript中并没有类的概念,不过ECMAScript中的构造函数可以用来创建特定类型的对象。因此,在JavaScript中可以创建自定义的构造函数,并且通过new操作符来创建对象。

2.原型代理:JavaScript中,一个对象可能包含一个指向原型的引用,该原型被称为代理。如果某个属性不存在于当前对象中,就会查找其代理原型。代理原型本身也会有自己的代理原型。这样就形成了一条原型链,沿着代理链向上查找,直到找到该属性,或者找到根代理Object.prototype为止。原型就是这样,通过使用new关键字来创建实例以及Constructor.prototype前后勾连成一条继承链。当然,也可以使用Object.create()来达到同样的目的,或者把它和拼接继承混用,从而可以把多个原型精简为单一代理,也可以做到在对象实例创建后继续扩展。

 

3.函数继承:在JavaScript中,任何函数都可以用来创建对象。如果一个函数既不是构造函数,也不是 class,它就被称为工厂函数。函数继承的工作原理是:由工厂函数创建对象,并向该对象直接添加属性,借此来扩展对象(使用拼接继承)。函数继承的概念最先由道格拉斯·克罗克福德提出,不过这种继承方式在JavaScript中却早已有之。

在JavaScript中并没有“指定的”构造函数类型,构造函数实质上也是函数,它和一般函数的区别只在于调用方式不同。只有当通过new操作符来调用的时候它才可以作为构造函数来创建对象实例,并且把构造函数的作用域赋给这个新对象(将this指向这个新对象)。如果没有使用new来调用构造函数,那就是普通的函数调用,这个时候this指向的是window对象,这样做会导致所有的属性和方法被添加到全局,因此一定要注意命名构造函数时首字母大写,并且永远使用new来调用它。

借助构造函数实现继承(经典继承)

 

 function Parent1() { this.name = 'parent1'; } Parent1.prototype.say = function () {} function Child1() { Parent1.call(this); this.type = 'child'; } console.log(new Child1);

复制代码

这个主要是借用call 来改变this的指向,通过 call 调用 Parent ,此时 Parent 中的 this 是指 Child1。有个缺点,从打印结果看出 Child并没有say方法,所以这种只能继承父类的实例属性和方法,不能继承原型属性/方法。注意 constructor 属性, new 操作为了记录「临时对象是由哪个函数创建的」,所以预先给「Child.prototype」加了一个 constructor 属性:

function Person(name, gender) {

借助原型链实现继承

    this.name = name;

function Parent2() { this.name = 'parent2'; this.play = [1, 2, 3];}function Child2() { this.type = 'child2';}Child2.prototype = new Parent2();console.log(new Child2);

    this.gender = gender;

通过一讲的,我们知道要共享莫些属性,需要 对象.__proto__ = 父亲对象的.prototype,但实际上我们是不能直接 操作__proto__,这时我们可以借用 new 来做,所以Child2.prototype = new Parent2(); = Child2.prototype.__proto__ = Parent2.prototype; 这样我们借助 new 这个语法糖,就可以实现原型链继承。缺点:给 s1.play新增一个值 ,s2 也跟着改了。所以这个是原型链继承的缺点,原因是 s1.__pro__ 和 s2.__pro__指向同一个地址即父类Child2的prototype。

   this.say = function() {

组合继承

    console.log("Hello");

是指将原型链和构造函数的相结合,发挥二者之长的一种继承模式。其思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,即通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。

  }

初级版

}

function Super(name){ this.name = name; this.colors = ["red", "blue", "green"];}Super.prototype.sayName = function (){ alert(this.name);};function Sub(name, age){ Super.call(this, name); //继承了Super 属性 (第二次调用Sup构造函数) this.age = age;}// 继承了Super 原型链上的方法 (第一次调用Sup构造函数) 注意后面需要改造这里,因为我们只想要方法,却生成了属性Sub.prototype = new Super(); Sub.prototype.constructor = Sub;// Sub.prototype.sayAge = function (){ alert(this.age);};var instance1 = new Sub("Luke", 18);instance1.colors.push("black");alert(instance1.colors); //"red, blue, green, black"instance1.sayName(); //"Luke"instance1.sayAge() //18var instance2 = new Sub("Jack", 20);alert(instance2.colors); //"red, blue, green"instance2.sayName(); //"Jack"instance2.sayAge() //20

 

优化后的组合继承

var person1 = new Person("Mike", "male");

function Super(name){ this.name = name; this.colors = ["red", "blue", "green"];}Super.prototype.sayName = function (){ alert(this.name);};function Sub(name, age){ Super.call(this, name); //继承了Super 属性 this.age = age;}function F(){}F.prototype = Super.prototype; Sub.prototype = new F(); // 继承了Super 原型链上的方法Sub.prototype.constructor = Sub;Sub.prototype.sayAge = function (){ alert(this.age);};var instance1 = new Sub("Luke", 18);console.log(instance1 )instance1.colors.push("black");alert(instance1.colors); //"red, blue, green, black"instance1.sayName(); //"Luke"instance1.sayAge() //18var instance2 = new Sub("Jack", 20);alert(instance2.colors); //"red, blue, green"instance2.sayName(); //"Jack"instance2.sayAge() //20

var person2 = new Person("Kate", "female");

疑问

复制代码

为什么要这么写?

这段代码就定义了一个构造函数Person, 并且给它添加了name和gender属性以及say方法。通过调用new操作符来创建了两个Person的实例person1和person2.可以通过代码来验证一下:

function F(){}F.prototype = Super.prototype; Sub.prototype = new F(); // 继承了Super 原型链上的方法

 

而不是

person1 instanceof Person; //true;

Sub.prototype = Super.prototype;

person2 instanceof Person; //true;

下面的方法没有办法区分一个对象是直接由它的子类实例化还是父类呢?下面这是第一个方法无法判断

并且person1和person2都分别具有了name,gender属性,并且都被附上了构造对象时传入的值。同时它们也都具有say方法。

instance1 instanceof Sub//trueinstance1 instanceof Super//true

 

我们还有一个方法判断来判断对象是否是类的实例,那就是用 constructor,我在控制台打印以下内容也无法分辨:

不过通过比较可以看出来,虽然这时person1和person2都具有say方法,但它们其实并不是同一个Function的实例,也就是说当使用new来创建构造函数的实例时,每个方法都在实例上重新被创建了一遍:

原型继承

 

借助原型可以基于已有的对象创建新对象, 同时还不必因此创建自定义类型在object()函数内部, 先创建一个临时性的构造函数, 然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例.

person1.say == person2.say; //false

function object(o){ function F(){} F.prototype = o;//重写F的原型,将他指向传入的o,这就相当于继承自o return new F();//返回F的实例对象}var person = { friends : ["Van","Louis","Nick"]};var anotherPerson = object(person);anotherPerson.friends.push("Rob");var yetAnotherPerson = object(person);yetAnotherPerson.friends.push("Style");alert(person.friends);//"Van,Louis,Nick,Rob,Style"

这样的重复创建Function是没有必要的,甚至在实例变多的时候造成一种浪费。为此,我们可以使用构造函数的prototype属性来解决问题。prototype原型对象是用来寻访继承特征的地方,添加到prototype对象中的属性和方法都会被构造函数创建的实例继承,这时实例中的方法就都是指向原型对象中Function的引用了:

从本质上讲, object() 对传入其中的对象执行了一次浅复制.所用的子类都指向传入的person对象

 

object.create() 方法规范化了上面的原型式继承. 上篇文章有这个方法的详细解释

复制代码

var person = { friends : ["Van","Louis","Nick"]};var anotherPerson = Object.create(person);anotherPerson.friends.push("Rob");var yetAnotherPerson = Object.create(person);yetAnotherPerson.friends.push("Style");alert(person.friends);//"Van,Louis,Nick,Rob,Style"console.log(anotherPerson)

function Person(name, gender) {

缺点:

    this.name = name;

原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。无法传递参数

    this.gender = gender;

寄生式继承

}

核心:在原型式继承的基础上,增强对象,返回构造函数函数的主要作用是为构造函数新增属性和方法,以增强函数

 

function createAnother(original){ var clone = object(original); // 通过调用 object() 函数创建一个新对象,object是一个任何能够返回对象的函数 clone.sayHi = function(){ // 以某种方式来增强对象 alert("hi"); }; return clone; // 返回这个对象}var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"]};var anotherPerson = createAnother(person);anotherPerson.sayHi(); //"hi"

Person.prototype.say = function() {

缺点(同原型式继承):原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。无法传递参数

    console.log("Hello");

}

 

var person1 = new Person("Mike", "male");

var person2 = new Person("Kate", "female");

 

person1.say == person2.say //true

复制代码

 

 

  1. prototype, constructor, 和__proto__

 

构造函数,原型对象,实例的关系是:JavaScript中,每个函数都有一个prototype属性,这是一个指针,指向了这个函数的原型对象。而这个原型对象有一个constructor属性,指向了该构造函数。每个通过该构造函数创建的对象都包含一个指向原型对象的内部指针__proto__。

 

 

 

用代码表示它们的关系:

 

Person.prototype.constructor === Person;

person1.__proto__ === Person.prototype;

person2.__proto__ === Person.prototype;

 

 

  1. 继承的实现

 

JavaScript中使用原型链作为实现继承的主要方法。由于对象实例拥有一个指向原型对象的指针,而当这个原型对象又等于另一个类型的实例时,它也具有这个指向另一类型原型对象的指针。因此通过指向原型对象的指针__proto__就可以层层递进的找到原型对象,这就是原型链。通过原型链来完成继承:

 

function Teacher(title) {

    this.title = title;

}

Teacher.prototype = new Person();

 

var teacher = new Teacher("professor");

这时,我们通过将Teacher的prototype原型对象指向Person的实例来完成了Teacher对Person的继承。可以看到Teacher的实例teacher具有了Person的属性和方法。

 

但是如果只是将构造函数的prototype原型对象指向另一对象实例,发生的事情其实可以归纳为:

 

 

 

Teacher.prototype instanceof Person //true

Teacher.prototype.constructor == Person //true

Teacher.prototype.__proto__ === Person.prototype //true

问题出现了:这时Teacher的构造函数变成了Person。虽然我们在使用创建的实例的属性和方法的时候constructor的类型并不会产生很大的影响,但是这依然是一个很不合理的地方。因此一般在使用原型链实现继承时,在将prototype指向另一个构造函数的实例之后需要再将当前构造函数的constructor改回来:

 

Teacher.prototype = new Person();

Teacher.prototype.constructor = Teacher;

这样才是真正的实现了原型链继承并且不改变当前构造函数和原型对象的关系:

 

 

 

到这里,我们就可以将这个继承过程封装成一个extend方法来专门完成继承的工作了:

 

var extend = function(Child, Parent) {

  Child.prototype = new Parent();

  Child.prototype.constructor = Child;

  return new Child();

};

现在这个方法接受两个参数:Child和Parent,并且在完成继承之后实例化一个Child对象并返回。我们当然可以根据需要来继续丰富这个函数,包括实例化的时候需要传入的参数什么的。

...

本文由yzc216亚洲城发布于网站首页,转载请注明出处:Js世袭总结

关键词: yzc216亚洲城