(一)构造函数
在说原型和原型链之前先了解一下什么是构造函数,构造函数与普通函数有什么区别
任何函数通过new操作符来调用,就可以说它是构造函数,构造函数没有return,一般用大写字母开头,不通过new调用的函数都是普通函数,一般用小写字母开头。
function Person(name,age,sex){ this.name = name this.age = age this.sex = sex } Person.prototype.getName = function(){ console.log(this.name) } // 当作构造函数使用 let p1 = new Person('lemon',18,'女') //this指向的p1 console.log(p1.name,p1.age,p1.sex) //lemon 18 女 //普通函数调用 let p2 = Person('lemon2',15,'女') //this指向的window console.log(window.name,window.age,window.sex) //lemon2 15 女
(二)原型对象
每个函数都有一个原型对象(prototype),我们称为显式原型对象,
每个实例化的对象都有一个__proto__对象,我们称为隐式原型对象
解释一下实例化的对象就是new操作符调用的函数返回的对象,(下篇文章讲解new怎么实现返回一个对象的),也就是new一个构造函数产生的就是一个实例化对象
1.给原型对象添加属性和方法 function Fun(name, age) { this.name = name this.age = age } // 给构造函数Fun的原型对象添加方法和属性 Fun.prototype.sex = '女' //添加属性 Fun.prototype.getAge = function () { //添加方法 console.log(this.age) }
2.使用new对构造函数Fun进行实例化,产生实例化对象fn
function Fun(name, age) { this.name = name this.age = age } //给构造函数Fun的原型对象 添加方法和属性 Fun.prototype.sex = '女' //添加属性 Fun.prototype.getAge = function () { //添加方法 console.log(this.age) } //产生实例化对象 let fn = new Fun('lemon', 18) console.log(fn.sex) //女
打印结果:
我们可以看出实例化后的fn对象的__proto__和构造函数的Fun的prototype对象是一样的。
console.log(fn, Fun.prototype) console.log(fn.__proto__ === Fun.prototype) //true
我们可以来分析一下 打印出来的实例化出来的对象,和构造函数的prototype原型对象,我们可以看出,实例化对象有一个__proto__对象指向的对应构造函数的prototype
实例化对象的隐式原型的值对应的是构造函数显示原型对象的值,即 fn.__proto__ === Fun.prototype
3.重写原型对象
当在实例化后再重写显示原型对象时,此时的原型对象已经指向新的对象,切断了构造函数与最初原型之间的联系
function Stu(name) { this.name = name this.age = age } Stu.prototype.getName = function () { console.log(this.name) } let s1 = new Stu('lemon') //重写prototype Stu.prototype = { age: 18, job: "it", getAge: function () { console.log(this.age) } } s1.getName() //lemon s1.getJob() //报错了!!
但并不会影响到在重写prototype前的实例化的隐式原型对象所指的显示原型对象,那个最初的显示原型对象还在,不会被覆盖,因为隐式原型对象的指针指向的是显式原型对象而不是构造函数 ,可以看图,加深理解
4.原型对象的缺点
我们把所有的构造函数里的属性和方法都写在prototype上面,这样确实省了在构造函数传参这一环节,但是当我们创建构造函数的实例的时候
prototype实例都是共享的,所有实例都获取到了相同的值,某种程度上带来一些不方便
function Person(){ } //把所有的属性和方法都写在了protottype上 Person.prototype = { name:"lemon", age:18, job:['html','css','JS'], getAge:function(){ console.log(this.name) } } let p1 = new Person() let p2 = new Person() p1.job.push('vue') console.log(p1.job) // ["html", "css", "JS", "vue"] console.log(p2.job) //["html", "css", "JS", "vue"] console.log(p1.job === p2.job) //true
我们通过向实例化对象p1的job数组添加了'vue',打印出来看到p2的job也改变了,所有的实例都会共享这个数组,
但是实例一般都有只属于自己的属性。所以原型对象好处也是数据的共享,缺点也是因为实例共享问题,但是这个问题可以改善的。
我们可以利用构造函数模式和原型模式结合,构造函数可以定义实例的属性,这是不被其他实例给共享的,共同的属性和定义的方法就可以写在原型上。
每个实例都有只属于自己的属性,同时也拥有的原型上面的方法,很大程度上节省了内存,直接修改上面的例子
function Person(name,age,job){ this.name = name this.age = age this.job = job } Person.prototype = { getAge:function(){ console.log(this.name) } } let p1 = new Person('lemon',18,["JS"]) p1.job.push('vue') let p2 = new Person('lemon2',19,['JS']) console.log(p1.job) //["JS", "vue"] console.log(p2.job) //["JS"] console.log(p1.job === p2.job) //false
上面的例子中将实例的属性定义在构造函数上,方法定义在原型上,
修改了p1.job(向其中添加一个新字符串),并不会影响到p2.job因为它们分别引用了不同的数组。
(三)原型链
(1) 在构造函数,实例和原型三者关系中,我们知道,实例对象的隐式原型对象(__proto__)指向构造函数的原型对象,当原型对象作为另一个构造函数的实例化对象,此时原型对象的隐式原型对象又指向的另一个构造函数的的原型,该函数的原型又是其他构造函数的实例,又产生了隐式原型,这样层层递进就构成了实例和原型的链条,就构成了原型链,所以原型链其实由隐式原型对象构成的
首先对象查找一个属性,会在自身上找,找不到就沿着隐式原型链(__proto__)上找,找到就返回,找不到就返回undefined
我们可以来看个例子
function Person(){ this.job = 'IT' }// 在原型上定义一个方法Person.prototype.getPersonJob = function(){ console.log(this.job)}function Student(name,age){ this.name =name, this.age = age}Student.prototype.getAge = function(){ console.log(this.age)}// Student.prototype原型对象的__proto__指向了Person.prototypeStudent.prototype = new Person()// 实例s1的__proto__指向Student.peototypelet s1 = new Student('tom',12)s1.getPersonJob()//IT
在该例子中我们定义了两个构造函数Person,Student Student.prototype = new Person() 这行代码我们将Student.prototype原型对象的__proto__指向了Person.prototype,此时我们就可以拿到了Person.prototype上的方法getPersonJob()
let s1 = new Student('tom',12),s1的__proto__指向了 Student.prototype,Student.prototype的__proto__指向了Person.prototype所以沿着__proto__上找,就拿到了getPersonJob()的方法
(2)如果构造函数的原型没有被其他构造函数实例化,它的__proto__指向的是谁??
记住几个原则
1. 函数的原型对象默认是Object的实例
2. 所有函数都是Function的实例
3. Object的原型对象是原型链的尽头
function fun(){}//1. 函数的原型对象默认指向的Objectconsole.log(fun.prototype instanceof Object) //true//2. 所有函数都是Function的实例console.log(fun instanceof Function) //true//3. Object的原型对象是原型链的尽头console.log(Object.prototype.__proto__) //null
相信看到此 就了解到了平时我们用到对象的toString(),valueOf()方法都是存储在Object.prototype上
还有修改函数this的指向call和apply方法,也是写在Function.prottotype上的,所以也是可以访问到的
原型链常用于对象继承,理解了原型链,基本可以理解各种对象继承的优缺点了。可以去看看对象的继承, 这里贴对象继承的几种方式
原型链继承
function Per(name){ this.name = 'lemon'}Per.prototype.getName = function(){ console.log(this.name)}function Stu(job,age){ this.job = job this.age = age}Stu.prototype = new Per()Stu.prototype.getJob = function(){ console.log(this.job)}let s2 = new Stu('hehe',18)s2.getName() //lemon
原型链继承缺点就是无法向父元素传参,所有实例都共享着原型对象的对象
构造函数继承
//构造函数继承function Per(name,price){ this.name = name this.price =price}Per.prototype.getName = function(){ console.log(this.name)}function Stu(name,price,job){ // 得到父类属性 Per.apply(this,[...arguments]) this.job = job // this.age = age}// 得到父类的方法Stu.prototype = new Per()Stu.prototype.getJob = function(){ console.log(this.job)}let s3 = new Stu('TOM',12,1200)console.log(s3.price) //12000s3.getName() //TOM
构造函数继承优点解决了所有实例共享数据的问题,也可以向父元素传参,缺点就是进行了两次的实例。
什么是构造函数,和普通函数有什么区别?
说一下隐式原型和显示原型?
在实例化之后又重写了原型对象,会造成什么后果?
什么是原型链?
对象继承都有几种方式?
你看懂了吗?
人类从来没有一天躲过台风,人类从来没有一天没有地震,我们永远有灾难永远有悲伤,可是我们过来了 对不对 ——黄执中
今儿就总结到此啦,若文章内容有何错误,后台反馈,欢迎指正!!
觉得不错可关注微信公众号哦