-
2021-08-13 20:58:13
一、前言
在js中,原型和原型链是一个很重要的知识点,只有理解了它,我们才能更深刻的理解js,在这里,我们将分成几个部分来逐步讲解。
二、构造函数
构造函数和普通函数本质上没什么区别,只不过使用了new关键字创建对象的函数,被叫做了构造函数。构造函数的首字母一般是大写,用以区分普通函数,当然不大写也不会有什么错误。
function Person(name, age) { this.name = name; this.age = age; this.species = '人类'; this.say = function () { console.log("Hello"); } } let per1 = new Person('xiaoming', 20);
三、原型对象
在js中,每一个函数类型的数据,都有一个叫做prototype的属性,这个属性指向的是一个对象,就是所谓的原型对象。
对于原型对象来说,它有个constructor属性,指向它的构造函数。
那么这个原型对象有什么用呢?最主要的作用就是用来存放实例对象的公有属性和公有方法。
在上面那个例子里species属性和say方法对于所有实例来说都一样,放在构造函数里,那每创建一个实例,就会重复创建一次相同的属性和方法,显得有些浪费。这时候,如果把这些公有的属性和方法放在原型对象里共享,就会好很多。
function Person(name, age) { this.name = name; this.age = age; } Person.prototype.species = '人类'; Person.prototype.say = function () { console.log("Hello"); } let per1 = new Person('xiaoming', 20); let per2 = new Person('xiaohong', 19); console.log(per1.species); // 人类 console.log(per2.species); // 人类 per1.say(); // Hello per2.say(); // Hello
可是这里的species属性和say方法不是实例对象自己的,为什么可以直接用点运算符访问?这是因为在js中,对象如果在自己的这里找不到对应的属性或者方法,就会查看构造函数的原型对象,如果上面有这个属性或方法,就会返回属性值或调用方法。所以有时候,我们会用per1.constructor查看对象的构造函数:
console.log(per1.constructor); // Person()
这个constructor是原型对象的属性,在这里能被实例对象使用,原因就是上面所说的。那如果原型对象上也没有找到想要的属性呢?这就要说到原型链了。
四、原型链
说原型链之前,先来了解两个概念:
1. 显示原型
显示原型就是利用prototype属性查找原型,只是这个是函数类型数据的属性。
2. 隐式原型
隐式原型是利用__proto__属性查找原型,这个属性指向当前对象的构造函数的原型对象,这个属性是对象类型数据的属性,所以可以在实例对象上面使用:
console.log(per1.__proto__ === Person.prototype); // true console.log(per2.__proto__ === Person.prototype); // true
根据上面,就可以得出constructor、prototype和__proto__之间的关系了:
3. 原型链
既然这个是对象类型的属性,而原型对象也是对象,那么原型对象就也有这个属性,但是原型对象的__proto__又是指向哪呢?
我们来分析一下,既然原型对象也是对象,那我们只要找到对象的构造函数就能知道__proto__的指向了。而js中,对象的构造函数就是Object(),所以对象的原型对象,就是Object.prototype。既然原型对象也是对象,那原型对象的原型对象,就也是Object.prototype。不过Object.prototype这个比较特殊,它没有上一层的原型对象,或者说是它的__proto__指向的是null。
所以上面的关系图可以拓展成下面这种:
到这里,就可以回答前面那个问题了,如果某个对象查找属性,自己和原型对象上都没有,那就会继续往原型对象的原型对象上去找,这个例子里就是Object.prototype,这里就是查找的终点站了,在这里找不到,就没有更上一层了(null里面啥也没有),直接返回undefined。
可以看出,整个查找过程都是顺着__proto__属性,一步一步往上查找,形成了像链条一样的结构,这个结构,就是原型链。所以,原型链也叫作隐式原型链。
正是因为这个原因,我们在创建对象、数组、函数等等数据的时候,都自带一些属性和方法,这些属性和方法是在它们的原型上面保存着,所以它们自创建起就可以直接使用那些属性和方法。
五、函数也是一种对象
函数在js中,也算是一种特殊的对象,所以,可以想到的是,函数是不是也有一个__proto__属性?答案是肯定的,既然如此,那就按上面的思路,先来找找函数对象的构造函数。
在js中,所有函数都可以看做是Function()的实例,而Person()和Object()都是函数,所以它们的构造函数就是Function()。Function()本身也是函数,所以Function()也是自己的实例,听起来既怪异又合理,但是就是这么回事。
console.log(Person.constructor === Function); // true console.log(Object.constructor === Function); // true console.log(Function.constructor === Function); // true
既然知道了函数的构造函数,那么函数的__proto__指向我们也就知道了,就是Function.prototype。
console.log(Person.__proto__ === Function.prototype); // true console.log(Object.__proto__ === Function.prototype); // true console.log(Function.__proto__ === Function.prototype); // true
根据这几个结论,我们就能拓展出一张更大的关系图了:
六、总结
- 构造函数是使用了new关键字的函数,用来创建对象,所有函数都是Function()的实例
- 原型对象是用来存放实例对象的公有属性和公有方法的一个公共对象,所有原型对象都是Object()的实例
- 原型链又叫隐式原型链,是由__proto__属性串联起来,原型链的尽头是Object.prototype
更多相关内容 -
一篇文章让你搞懂JavaScript 原型和原型链
2021-01-22 10:41:15原型和原型链作为深入学习JavaScript最重要的概念之一,如果掌握它了后,弄清楚例如:JavaScript的继承,new关键字的原来、封装及优化等概念将变得不在话下,那么下面我们开始关于原型和原型链的介绍。 什么是原型?... -
JS原型和原型链原理与用法实例详解
2020-11-21 08:53:49本文实例讲述了JS原型和原型链原理与用法。分享给大家供大家参考,具体如下: Javascript语言的继承机制一直很难被人理解。 它没有”子类”和”父类”的概念,也没有”类”(class)和”实例”(instance)的区分,... -
深入理解JavaScript系列(6) 强大的原型和原型链
2020-12-09 10:27:55由于 JavaScript 是唯一一个被广泛使用的基于原型继承的语言,所以理解两种继承模式的差异是需要一定时间的,今天我们就来了解一下原型和原型链。 原型 10年前,我刚学习JavaScript的时候,一般都是用如下方式来写... -
js 原型对象和原型链理解
2020-12-02 12:51:28之前对js中的原型链和原型对象有所了解,每当别人问我什么是原型链和原型对象时,我总是用很官方(其实自己不懂)的解释去描述。有一句话说的好:如果你不能把一个很复杂的东西用最简单的话语描述出来,那就说明你... -
javascript原型和原型链
2021-01-08 18:16:24一、原型规则 1、所有的引用类型(数组、对象、函数)都具有对象特性,即可自由扩展属性(除了“null”) var array=[];array.a=1; var object={};object.a=1; function func(){}; func.a=1; 2、所有的引用类型... -
JavaScript使用原型和原型链实现对象继承的方法详解
2020-12-12 13:07:02本文实例讲述了JavaScript使用...以下就是原型链和原型的关系,引用网上的一张图 在Javascript中,每个函数都有一个原型属性prototype指向自身的原型,而由这个函数创建的对象也有一个proto属性指向这个原型,而函数的 -
图解JS原型和原型链实现原理
2020-10-14 18:42:33主要介绍了图解JS原型和原型链实现原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 -
强大的原型和原型链
2018-07-24 09:21:04JavaScript 不包含传统的类继承模型,而是使用 prototypal 原型模型。...由于 JavaScript 是唯一一个被广泛使用的基于原型继承的语言,所以理解两种继承模式的差异是需要一定时间的,今天我们就来了解一下原型和原型链 -
跟我学习javascript的prototype原型和原型链
2021-01-21 11:32:13这个属性是指向一个对象的引用,这个对象称为原型对象,原型对象包含函数实例共享的方法和属性,也就是说将函数用作构造函数调用(使用new操作符调用)的时候,新创建的对象会从原型对象上继承属性和方法。... -
原型和原型链的理解
2019-09-09 09:39:47原型和原型链的理解:(面试题) 原型:每个函数都有 prototype 属性,该属性指向原型对象;使用原型对象的好处是所有对象实例共享它所包含的属性和方法。 原型链:主要解决了继承的问题;每个对象都拥有一个原型对象...原型和原型链的理解:(面试题)
- 原型:每个函数都有 prototype 属性,该属性指向原型对象;使用原型对象的好处是所有对象实例共享它所包含的属性和方法。
- 原型链:主要解决了继承的问题;每个对象都拥有一个原型对象,通过__proto__ 指针指向其原型对象,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null。
原型的作用:
1.数据共享 节约内存内存空间
2.实现继承
注意:函数也是一个对象,对象不一定是函数。(对象有__proto__属性,函数有prototype属性)此处说明,方便大家理解下文。
下面我将举例说明为什么要使用原型
例1:function Person(name) { this.name=name; this.eat=function () { console.log(this.name+"吃东西"); } this.sleep=function () { console.log(this.name+"睡觉"); } } var p1=new Person("小明"); p1.eat();//小明吃东西 p1.sleep();//小明睡觉 var p2=new Person("小利"); p2.eat();//小利吃东西 p2.sleep();//小利睡觉 console.dir(p1);//dir()打印结构 console.dir(p2);
每次使用构造函数Person()实例化出对象的时候,就会给每个实例对象的eat()方法和sleep()方法开辟空间。可是当实例化许多对象的时候,就会浪费大量的空间,因为每个实例对象的eat()方法和sleep()的功能都是一样的,所以我们没必要为每个实例对象添加eat()方法和sleep()方法。
这时原型就派上用场了,看下面经过改造的例子:
function Person(name) { this.name=name; } Person.prototype.eat=function () { console.log(this.name+"吃东西"); }; Person.prototype.sleep=function () { console.log(this.name+"睡觉"); } var p1=new Person("小明"); p1.eat();//小明吃东西 p1.sleep();//小明睡觉 var p2=new Person("小利"); p2.eat();//小利吃东西 p2.sleep();//小利睡觉 console.dir(p1); console.dir(p2);
eat()方法和sleep()被添加到了Person()构造函数的原型(prototype)上了。因此只有一份eat()方法和sleep()方法。当实例对象需要使用这些方法的时候就在自己的__proto__属性中找到并调用这些方法实现相应的功能。
现在我们来捋一下构造函数,实例对象,原型对象之间的关系。
如以下代码和图所示:
Person()构造函数的prototype属性是一个对象,实例对象p1的__proto__属性也是一个对象,并且prototype对象和__proto__对象的指向相同。那么我们再回过头来理解一下为什么添加到原型的方法可以是共享的。因为prototype对象和__proto__对象的指向相同,所以将eat()方法和sleep()添加到Person()构造函数的prototype属性上之后,实例对象就可以通过自己__proto__属性去访问eat()方法和sleep()了。
console.dir(Person); console.dir(p1); console.log(typeof p1.__proto__);//object console.log(typeof Person.prototype);//object console.log(p1.__proto__ === Person.prototype);//true
__proto__指向该对象所在的构造函数的原型对象。
实例对象和构造函数之间没用直接的关系。原型对象与实例对象之间用原型(__proto__)关联,这种关系叫做原型链。
我是这样理解原型链的(可能不是很准确)我向我爸要零花钱,我爸也没有钱,那么我就向我奶奶要,奶奶要是也没有,就继续找别人要。
那么原型的指向可以改变吗?答案是可以的。
举个例子:
function Person(name) { this.name=name; } Person.prototype.eat=function () { console.log(this.name+"吃东西"); }; Person.prototype.sleep=function () { console.log(this.name+"睡觉"); } function Student(school) { this.school=school; } Student.prototype.write=function () { console.log("写作业"); } Student.prototype=new Person("小华");//改变Student()构造函数的指向,让Student()构造函数的原型对象指向Person的实例对象 var s1=new Student("某某高中"); s1.eat();//小华吃东西 s1.sleep();//小华睡觉 s1.write();//Uncaught TypeError: s1.write is not a function,因为Student()的原型的指向改变,所以找不到write()方法 console.dir(Student); console.dir(s1);
__proto__指向该对象所在的构造函数的原型对象。如上图所示:Studend()构造函数的原型(prototype)指向了Person()的实例对象(new Person("小华")),所以Studend()的实例对象s1的__proto__也指向了Person()的实例对象((new Person("小华"))。而实例对象((new Person("小华"))的__proto__指向了其所在的构造函数Person()的原型对象在这个原型对象中,找到了eat()方法和sleep()方法。
从这个例子中,可以发现,利用原型可以实现继承。面向对象的编程语言中有(class)类的概念,但是JavaScript不是面向对象的语言,所以js中没有类(class)(ES6中实现了class),但是js可以模拟面向对象的思想编程,js中通过构造函数来模拟类的概念。
改变原型的指向可以实现方法的继承。借用构造函数继承,主要解决属性的问题
function Person(name) { this.name=name; } Person.prototype.eat=function () { console.log(this.name+"吃东西"); }; Person.prototype.sleep=function () { console.log(this.name+"睡觉"); } function Student(name,school) {//name为父类构造器传参。子类构造器可以添加自己特有的属性school Person.call(this,name);//调用父类构造器Person的属性, this.school=school; } Student.prototype.write=function () { console.log("写作业"); } Student.prototype=new Person();//改变Student()构造函数的指向,让Student()构造函数的原型对象指向Person的实例对象 var s1=new Student("zx","某某高中"); s1.eat();//小华吃东西 s1.sleep();//小华睡觉 console.dir(Student); console.dir(s1);
组合继承就是指:将改变原型的指向和借用构造函数两者结合在一起实现继承。
一、原型模式
我们创建的每一个函数都有一个
prototype
(原型)属性,这个属性指向的是通过调用构造函数来创建出来的对象实例的原型对象,这个原型对象可以让所有对象实例共享它所包含的属性和方法。function Person () { } Person.prototype.name = "xiao"; Person.prototype.sayName = function () { alert('this.name') } var person1 = new Person(); var person2 = new Person(); person1.sayName() console.log(person1.name == person2.name) // "true"
上面的例子当中我们创建了一个构造函数
Person
,并通过它的prototype
属性在它的原型对象中定义了name
属性并赋值,然后通过调用构造函数Person
实例化出来两个对象实例,通过打印出来的值我们可以得知,person1
和person2
共享了原型对象中的属性和方法。构造函数,原型对象和对象实例的关系
我们知道每个函数都有一个
prototype
属性指向函数的原型对象。在默认情况下,所有原型对象都有一个constructor
(构造函数)属性,这个属性指向了prototype
属性所在的函数,比如前面的例子中,Person.prototype.constructor
就指向Person
。
另外,当调用构造函数创建一个新实例后,该实例的内部将包含一个__porto__
属性(仅在Firefox、Safari、Chrome中支持),这个属性指向的就是构造函数的原型对象。由此我们可以得出以下图示的结论:通过代码来验证:
# 实例和原型对象之间的关系 console.log(person.__proto__ == Person.prototype) // true # 也可以通过isPrototypeOf()和ES5中的Object.getPrototypeOf()方法来判断 console.log(Person.prototype.isPrototypeOf(person1)) // true console.log(Object.getPrototypeOf(person) === Person.prototype) // true # 原型对象和构造函数的关系 console.log(Person.prototype.constructor == Person) // true
二、原型链
通过前面我们对构造函数,对象实例和原型对象三者关系的描述可知,实例都包含了指向原型对象的内部指针。
那么假如现在我们有两个构造函数A跟B,我们让构造函数A的原型对象等于构造函数B的实例,根据前面的推论,这个时候A的原型对象就包含指向B的原型对象的指针,再假如又有一个构造函数C,让A的原型对象等于C的实例,上述关系依旧成立,以此类推便形成了实例与原型的链条,即原型链,它主要作为JS中实现继承的主要方法。原型链的基本实现
function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function() { return this.property; } # 继承了SuperType SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function() { return this.subproperty; } var instance = new SubType(); console.log(instance.SuperValue()); // true
在上面的代码中,我们没有使用
SubType
默认的原型,而是将SuperType
的实例赋给它,重写了SubType
的原型对象;这样一来SubType.prototype
的内部便具有一个指向SuperType
原型的指针,原来存在于SuperType
的实例中的所有属性和方法,现在也存在于SubType.prototype
中了。instance
同理,还要注意的是由于SubType
的原型指向了SuperType
的原型,而SuperType
的原型的constructor
属性指向的是SuperType
构造函数,那么instance.constructor
也就指向了SuperType
原型搜索机制:
当访问一个实例属性或方法时,在通过原型链实现继承的情况下,首先会在实例中搜索该属性,在没有找到属性或方法时,便会沿着原型链继续往上搜索,直到原型链末端才会停下来。
这里还有一个重要的点,事实上所有引用类型默认都继承了Object
,而这个继承也是通过原型链实现的,也就是说,所有函数的默认原型都是Object
的实例,这也是所有自定义类型都会继承toString()
、valueOf()
等默认方法的根本原因。Object.prototype的原型
既然所有类型默认都继承了
Object
,那么Object.prototype
又指向哪里呢,答案是null
,我们可以通过下面的代码打印试试看:console.log(Object.prototype.__proto__ === null) // true
null
即没有值,也就是说属性或方法的查找到Object.prototype
就结束了。 -
JS:原型和原型链
2022-02-11 15:59:47一、原型 原型:每当定义一个对象(函数也是对象)时,就会生成一个__proto__属性,被称为隐式原型;这个__proto__属性指向的是这个对象的构造函数的prototype,被称为显式原型。每一个对象都会从原型中“继承”属性...一、原型
原型:每当定义一个对象(函数也是对象)时,就会生成一个__proto__属性,被称为
隐式原型
;这个__proto__属性指向的是这个对象的构造函数的prototype,被称为显式原型
。每一个对象都会从原型中“继承”属性。首先看一个例子,创建一个Student类,并创建类的实例对象student:
class Student{ constructor(name, score) { this.name = name; this.score = score; } introduce() { console.log(`我是${this.name},考了${this.score}分。`) } } const student = new Student('张三', 99) console.log('student', student); // student Student { name: '张三', score: 99} student.introduce(); // 我是张三,考了99分。
控制台能访问到属性和方法。
但是直接在控制台输入student,会发现只有name和score属性,没有introduce方法,但是有一个[[Prototype]]属性,用两个中括号括起来。
把[[Prototype]]展开,会发现introduce方法在[[Prototype]]中。
[[Prototype]]属性被称为student对象的
隐式原型
。当我们要去找一个对象的属性或者方法时,如果在当前对象上找不到,就会去当前对象的隐式原型
[[Prototype]]属性上去找。可通过.__proto__属性访问原型,注意__proto__两边分别有两条下划线。
而Student()构造函数也有一个prototype属性,Student()构造函数的prototype属性实际上就等于student对象的__proto__属性。
下面用一张图来说明:
因此,构造函数的prototype属性就等于实例对象的__proto__属性,构造函数的prototype属性被称为
显式原型
,实例对象的__proto__属性被称为隐式原型
。
二、原型链
原型链:当访问一个对象的属性或方法时,首先对象会从自身去找,如果找不到,就会往原型中去找,即__proto__,也就是它构造函数的prototype中;如果原型中找不到,即构造函数中也没有该属性,因为构造函数也是对象,也有__proto__,就会往原型的原型上去找,这样就形成了链式的结构,称为原型链,本质描述的是对象的一种继承关系。
再来看一个例子,创建一个Person类,再创建一个Teacher类继承自Person类,并创建实例对象teacher:
class Person { constructor(name) { this.name = name; } drink(){ console.log('喝水'); } } class Teacher extends Person { constructor(name, subject) { super(name); this.subject = subject; } teach() { console.log(`我是${this.name}, 教${this.subject}。`) } } const teacher = new Teacher('哈默', '前端开发') console.log('teacher', teacher); teacher.teach(); teacher.drink();
控制台输出如下,teacher可以执行teach()和drink()方法。
展开teacher对象,发现找不到这两个方法,于是去找对象的原型,即__proto__属性,找到teach()方法,再展开下一层__proto__属性,找到drink()方法。
下面用一张图说明:
可以看到,teacher实例对象本身是没有teach()方法的,这时就会去teacher对象的__proto__隐式原型指向的Teacher.prototype显式原型上去找,此时找到了teach()方法并执行;同时,Teacher.prototype上仍然没有找到drink()方法,而Teacher.prototype也是一个对象,有自己的__proto__隐式原型,那么就去Teacher.prototype.__proto__上去找,Teacher.prototype.__proto__会指向Person()构造函数的显式原型Person.prototype,此时找到了drink()方法并执行,这就是
原型链
。注:
(1)通过__proto__形成原型链而非protrotype。
(2)__proto__属性是对象所独有的。
(3)prototype属性是函数所独有的。但是由于JS中函数也是一种对象,所以函数也拥有__proto__属性。
三、判断对象自身是否有某属性或方法
hasOwnProperty()方法会返回一个布尔值,用于判断对象自身是否有某属性或方法。返回true,代表是该对象自身的属性或方法;返回false,代表是该对象原型上的属性或方法。
由于Person类继承自Object类,那么执行teacher.hasOwnProperty()方法时,实际会找到Object.prototype中的hasOwnProperty()方法并执行。
因此,所有继承了Object的对象都会继承到hasOwnProperty方法。
同时可以看到,Object.prototype.__proto__ 的值为 null ,即 Object.prototype 没有原型,所以可以想象在原型链中,当找到顶层原型还没有属性时,那就是没有这个属性,返回返回undefined。
四、使用instanceof作类型判断
instanceof 运算符:用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
看一个例子,使用typeof判断array的数据类型时,返回的是object,因此无法使用typeof判断array的类型。
const object = {}; const array = []; // 使用typeof判断数据类型,array返回的是object console.log(typeof object); // object console.log(typeof array); // object
下面使用instanceof运算符判断array的数据类型:
// 使用instanceof判断数据类型 const flagObject = object instanceof Array; const flagArray = array instanceof Array; console.log(flagObject); // false console.log(flagArray); // true
object instanceof Array返回false,表示Array.prototype不在object的原型链上;array instanceof Array返回true,表示Array.prototype在array的原型链上,由此可以区分object和array的数据类型。
也可通过控制台查看object和array的原型。
注:[] instanceof Object 为 true
-
原型和原型链详解
2020-06-04 18:29:03原型和原型链怎么来的? 1994年,网景公司(Netscape)发布了Navigator浏览器0.9版,但是刚开始的Js没有继承机制,更别提像同时期兴盛的C++和Java这样拥有面向对象的概念。在实际的开发过程中,构造函数内部的属性...原型和原型链怎么来的?
1994年,网景公司(Netscape)发布了Navigator浏览器0.9版,但是刚开始的Js没有继承机制,更别提像同时期兴盛的C++和Java这样拥有面向对象的概念。在实际的开发过程中,构造函数内部的属性方法,每次new一个实例对象的时候,都会创建内部的这些属性和方法,并且不同的实例对象之间,不能共享这些方法,造成了资源的浪费。工程师们发现没有继承机制很难解决一些问题,必须有一种机制能将所有的对象关联起来。于是有了原型这个概念。
Brendan Eich鉴于以上情况,但不想把Js设计得过为复杂,于是引入了new关键字 和 constructor构造函数来简化对象的设计,引入了prototype函数对象来包含所有实例对象的构造函数的属性和方法,引入了proto和原型链的概念解决继承的问题
一、原型是什么
每个函数都有一个prototype(原型)属性,这个属性都有一个指针,指向一个对象,这个对象包含由特定类型所有实例共享的属性和方法,我这里就给他安置了一个辈分——对象的父亲。瞬间逼格就高了一级,哈哈哈哈...使用原型的好处是什么呢? 可以让所有对象实例共享原型包含的方法和属性。利用原型是当前构造函数创建的对象的父类,这个特点我们可以提取对象的公共属性和方法放在原型中,从程序的视角中具有封装性
1.1 原型的构成:原型的属性和方法+constructor
function Car(color) { this.color = color } Car.prototype.name = "BMW"; Car.prototype.setColor = function (color) { this.color = color } var car = new Car()
从上面可以看到:car是构造函数创建的对象;
Car是构造函数;
Car.prototype是car对象的父亲,这也就不难理解对象身上没有name和setColor的属性和方法,car对象却能访问的缘故了。
二、如何使用原型
构造函数创建的实例可以访问构造函数的属性和方法,也可以访问原型的属性和方法。对于原型只能访问自己的属性和方法。考虑这一特性,如果对象公有的属性和方法,我们可以添加到原型上面,对象需要的时候直接访问原型的属性和方法就可以。例子如下:setAge的方法代表所有动物都设置自己的年龄的功能,所以我们放在原型上面,但是现实生活并不是这样的,这个例子举得不太合适了,嘻嘻...
function Animal(name) { this.name= "animal"; this.age= 18; this.setName= function (name) { this.name = name } } Animal.prototype.setAge =function(age){ this.age = age } var dog = new Animal("dog") var cat = new Animal("cat")
总结下:对象共性的属性和方法我们需要放在原型上
三、了解构造函数、原型、 对象之间的关系(重要)
注:构造函数创建的对象调用的是__proto__,原型调用的是prototype
四、new关键字原理解析
对于对象的创建,new关键字处于举足轻重的地位,我们简单的阐述下。
Car.prototype={ name : "BMW", width :"1400", health:100, run : function(){ this.health-- } } function Car (color){ //var this ={ // car.__prote__ : Car.prototype; // } this.color =color // return this; } var car = new Car('red'); var car1 = new Car('green');
17、 18行使用new关键字之后在构造函数中声明var this ={},然后car.__prote__放到this对象里面,最后把对象返回出来。注意如果人为恶意的修改返回值,返回值修改为其他对象的话会返回相应的对象,如果修改为基本数据类型的话不起作用。
注意:并不是所有的对象对会继承自object.protetype,特例Object.create(null);
至此原型告一段落。接下来我们看一下原型链
五、原型链
当对象访问属性和方法的时候,会往自身查找,如果没有才会去原型中找。(一级一级传递 形成了原型链)
原型链实现:让原型等于父类构造函数创建的对象
原型链的本质:子类构造函数的
prototype
的__proto__
指向父类构造器的prototype
,建立对象之间的关联我们通过下图和代码可以验证上面结论
function GrandFather(name) { this.name = name; this.age = 18; this.setName = function (name) { this.name = name } } GrandFather.prototype.setAge = function (age) { this.age = age } function Father() { } function Son() { } var grandFather = new GrandFather("grandFather") Father.prototype = grandFather;// equal to Object.setPrototypeOf(grandFather, Father.prototype) var father = new Father(); Son.prototype = father // equal to Object.setPrototypeOf(father, Son.prototype) var son = new Son(); console.log(son.__proto__ === Son.prototype) console.log(Son.prototype.__proto__ === Father.prototype) console.log(Father.prototype.__proto__ === GrandFather.prototype)
缺点:这种方式不难发现继承了很多自己不需要的属性会导致效率、性能等问题。其次主要是一层套一层太繁琐
于是乎我们想到了call、apply可以改变this的指向,借助别人的方法实现自己的功能,但是它也有问题,不能使用其他构造函数的方法,这个时候我么就想到了Son的构造函数和GrandFather的构造函数指向同一个原型,但是这样又出现一个问题就是Son修改原型的数据会导致GrandFather的原型数据也会改变,因为他俩指向同一个内存地址,这是我们不想看到的,于是乎我们就找到了最完美的解决方案:圣杯模式
function inherit(Target, Origin) { function F() { } F.prototype = Origin.prototype Target.prototype = new F() Target.prototype.constructor = Target }
圣杯模式的演变解决了以下问题:
1)虽然实现了继承但相互影响各自的数据
2)构造函数指向问题
上面的方法赋值之后Origin.prototype和F.prototype指向同一个内存空间,Target.prototype和new F()指向同一个内存空间,修改Origin.prototype的时候F.prototype会改变,因为原型链的存在导致new F()的内存地址的内容会改变,进而Target.prototype改变。但是修改Target.prototype内存地址的属性只会导致new F()的内存地址的属性会改变,原型链是不可逆的,从而解决了可以随意修改子类的属性而不用担心父类的会改变
如果不考虑性能的话上面的圣杯模式实现是没问题的
如果使用ES5继承的话建议使用
Object.create()使用现有的对象来提供新创建的对象的__proto__
不了解
Object.create()
可移步:https://www.yuque.com/taowuhua/gfneg0/vfz5sffunction GrandFather(name,age) { this.name = name; this.age = age; } GrandFather.prototype.setAge = function (age) { this.age = age } GrandFather.prototype.setName = function (name) { this.name = name } function Son() { } Son.prototype = Object.create(GrandFather.prototype); Son.prototype.constructor = Son; var son = new Son(); console.log(son.setAge) console.log(son.name)
ES6中通过extends实现继承:直观,代码组织更加清晰
class GrandFather { constructor(name, age) { this.name = name; this.age = age; } modifyName(name) { this.name = name; } eatFood() { } } class Son extends GrandFather{ constructor(name,age,height){ //尽量把 super 写在第一行 super(name, age);//等价于 GrandFather.call(this, name, age) this.height = height; } readBook(){ } } var son = new Son("taowuhua",18,180)
虽然son没有modifyName这个方法,但是通过extends还是继承了GrandFather的方法。这个还是很容易理解的。
注意:内建类的坑
class MyArray extends Array { }
这种方式可以让开发者继承内建类的功能创造出符合自己想要的类。所有 Array 已有的属性和方法都会对继承类生效。这确实是个不错的诱惑,也是继承最大的吸引力。
但现实总是悲催的。
extends
内建类会引发一些奇怪的问题,很多属性和方法没办法在继承类中正常工作。举个例子:var a = new Array(1, 2, 3) a.length // 3 var b = new MyArray(1, 2, 3) b.length // 0
如果说语法糖可以用 Babel.js 这种 transpiler 去编译成 ES5 解决 ,扩充的 API 可以用 polyfill 解决,但是这种内建类的继承机制显然是需要浏览器支持的。而目前唯一支持这个特性的浏览器是………… Microsoft Edge 。
-
原型和原型链的介绍和使用
2021-04-03 10:43:35JavaScript原型一、原型的介绍二、原型的使用prototypeconstructor_proto_三、原型链 一、原型的介绍 原型是JavaScript中function对象的一个属性,它定义了构造函数制造出的对象的公共祖先,通过该构造函数产生... -
【重点】图解:告诉面试官什么是 JS 原型和原型链?
2019-10-24 09:10:32在我初学 JS 语言的继承机制原型和原型链的时候,我一直理解不了这种设计机制,再加上之前原有对 Java继承的理解,在学习 JS 继承机制的设计上踩了一个大坑,很多知识点前期都是死记硬背,无法真正的理解它的设计... -
JS中的原型和原型链(图解)
2018-09-09 11:55:09JS中的原型和原型链 讲原型的时候,我们应该先要记住以下几个要点,这几个要点是理解原型的关键: 1、所有的引用类型(数组、函数、对象)可以自由扩展属性(除null以外)。 2、所有的引用类型都有一个’_ _ ... -
理解javascript中的原型和原型链
2020-12-12 09:27:48原型 大家都知道,JavaScript 不包含传统的类继承模型,而是使用 prototype 原型模型。代码实现大概是这样子的 function Student(name){ this.name = name; } var Kimy = new Student("Kimy"); Student.... -
JavaScript原型和原型链详解
2021-11-02 15:33:42一、原型 ①所有 引用类型 都有一个 __proto__(隐式原型)属性,属性值是一个普通的对象 ...二、原型链 当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的 __pro -
JavaScript 函数、原型和原型链详解
2019-10-19 14:17:59原型和原型链详解 一、JS函数的四种含义: 首先执行一段代码: function Fun() {}; var fun1= new Fun(); console.log(fun1 instanceof Fun); //其执行结果为true;说明JavaScript里面确实存在着类与对象(fun1... -
学习javascript面向对象 理解javascript原型和原型链
2020-11-24 03:57:28【原型链】每个构造函数都有一个对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么,如果原型对象等于另一个原型的实例,此时的原型对象将包含一个指向另一个原型的指针,... -
简单理解原型和原型链
2021-02-15 14:55:18原型和原型链是我们在面试中必会提问到的一个问题,理解原型和原型链不管对学习还是工作都会得到帮助,希望这篇文章会帮助你理解原型和原型链。 一、原型对象 原型对象分为两种,隐式原型(proto)和显式原型... -
什么是原型、原型链?原型和原型链的作用(有图方便理解)
2021-04-23 09:55:47原型:JS声明构造函数(用来实例化对象的函数)时,会在内存中创建一个对应的对象,这个对象就是原函数的原型。构造函数默认有一个prototype属性,prototype的值指向函数的原型。同时原型中也有一个constructor属性,...