js原型和原型链_JS的原型和原型链(JS基础四)

(一)构造函数

在说原型和原型链之前先了解一下什么是构造函数,构造函数与普通函数有什么区别

任何函数通过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) //女

打印结果:

2aa77a8ef55f0f1471d75f14a55dc850.png

我们可以看出实例化后的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前的实例化的隐式原型对象所指的显示原型对象,那个最初的显示原型对象还在,不会被覆盖,因为隐式原型对象的指针指向的是显式原型对象而不是构造函数 ,可以看图,加深理解

bc7f43c2a736dc8771bcc61c5c85db44.png

57642a4215b3d2a1092febbf37735dcd.png

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

构造函数继承优点解决了所有实例共享数据的问题,也可以向父元素传参,缺点就是进行了两次的实例。

什么是构造函数,和普通函数有什么区别?

说一下隐式原型和显示原型?

在实例化之后又重写了原型对象,会造成什么后果?

什么是原型链?

对象继承都有几种方式?

你看懂了吗?

人类从来没有一天躲过台风,人类从来没有一天没有地震,我们永远有灾难永远有悲伤,可是我们过来了 对不对  ——黄执中

今儿就总结到此啦,若文章内容有何错误,后台反馈,欢迎指正!!

觉得不错可关注微信公众号哦

4532a8b6010940623fc8786389305eb0.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值