原型,原型链,call/apply

(一)原型

  1. 原型的概念解析

(1)概念:原型是function对象的一个属性①,它定义了构造函数制造出的对象的公共祖先②,通过该构造函数产生的对象,可以继承该原型的属性和方法③,原型也是对象

(2)解析:

 

       //① Person是一个构造函数,也是function对象 

        Person.prototype   // prototype是function对象的一个属性

        function Person(){}

        var person1 = new Person();

     var person1 = new Person();  //构造函数制造出来的对象

        var person2 = new Person();  //构造函数制造出来的对象

        //② Person.prototype 是person1和person2的公共祖先

Person.prototype = {

            name:'小张',

            age:29,

            sayName:function(){

                console.log(this.name);

            }

        }  

        function Person(){}

        var person1 = new Person();  

        var person2 = new Person();  

        console.log(person1.name);  //小张

        console.log(person2.sayName());  //小张

        //③ person1和person2身上没有属性,它是继承了它原型上的属性和方法

 

(3)补充:

① 是函数它都有原型

② 原型在构造函数/函数出生时,系统就定义好了

③ 原型也是一个对象

④ 对象也是由构造函数构造出来的

  1. 原型的特点

(1) 如果对象/构造函数构造出的对象自己身上有这个属性和方法,就用自己的,如果没有就用原型的(祖先)(就近原则)

(2) 对象既可以有自己的属性和方法,也可以有原型为它提供的属性和方法

  1. 原型的应用

(1) 可以把共有的属性和方法放到原型上去,每一次创建对象时,就可以去他的祖先(原型)那里继承了

(2) 代码说明

 

        Car.prototype = {  //原型上放着car1和car2的共有属性和方法

            carName:'Audi',

            height:1500,

            lang:3800,

            type:{

                one:'A',

                two:'B'

            }

        }

        function Car(color,owner){

            this.owner = owner;

            this.color = color;

        }

 

        var car1 = new Car('red','小明');

        var car2 = new Car('blue','晓东');

 

 

 

 

  1. 原型的增删改查

(1) 查:对象名.原型上的属性名 = ‘值’

(2) 增:构造函数名.prototype.要增加在原型上的属性名 = ‘值’

(3) 改:构造函数名.prototype.要修改的原型上的属性名 = ‘值’

(4) 删:delete  构造函数名.prototype.要删除的原型上的属性名

  1. 原型与对象上的一些属性

(1)constructor (构造器)

① 它是原型上一个自带的属性,返回构造对象的构造函数

② 使用方法:构造函数构造出的对象.constructor

③ 代码说明

 

function Demo(){

 

        }

        var demo = new Demo();

        console.log(demo.constructor);  //ƒ Demo(){}

 

 

④ 对于不是构造函数构造出的对象的对象调用这个属性上会返回Object构造函数

 

  var demo = {};

        console.log(demo.constructor);  //ƒ Object() { [native code] }

 

 

⑤ 不是构造函数构造出来的对象通过constructor属性可以看出其也是由构造函数构造而成的

 

var demo = {};  ===    function Object(){}; var demo  = new Object();

 

(2)__proto__

① 对象会自带一个属性__proto__,这个属性指向其对象的原型

② 在前面用构造函数构造对象时,要有一个new,有new后会有三步,当时还用that显示的模拟了这三步,而显示的模拟我们并不建议那么用,原因就是这个__proto__属性

 

 

 

③ 上面的构造函数构造对象时的第一步会创建一个this对象,其实在这个this对象里面就有__proto__属性

var this = {

            __proto__:该构造函数.prototype;

        }

 

④  上面说过对象如果去访问一个属性,这个属性如果自己有就用自己的,如果没有就用原型上的,那么访问原型上的这个属性的中间就是靠__proto__这个属性搭建的,具体实现过程见下面

 

 

 

 

⑤  可以通过动态修改这个属性,让对象指向其他的原型

 

  Car.prototype = {

            name:'car'

        }

        function Car(){

 

        }

        Demo.prototype = {

            name:'demo'

        }

        function Demo(){

 

        }

        var car = new Car();

        var demo = new Demo();

        console.log(car.__proto__);// {name: "car"}

        car.__proto__ = Demo.prototype;  //动态修改car的原型指向

        console.log(car.__proto__);  //{name:"demo"}

 

 

⑥  原型的对象问题展示

 

代码一

      Person.prototype.name = '小猪';  //①

         function Person(){

             //var this = { __proto__:Person.prototype }  //这是一个空间,指向①处

         }

         var person1 = new Person();   //这里通过构造函数构造对象,这里的原型在①处

         

 

         //下面会新建一个空间,让其指向②处

         Person.prototype = {   //② 

             name:'小龙'        //这是一个新的原型对象,和①没有任何关系  这是一个新建的空间

         }

        //   __proto__:Person.prototype  //让__proto__这个属性指向②处新建的空间

         Person.prototype.name = '小花';    //③  这是在②对象的属性上改变属性值

         var person2 = new Person();

         console.log(person1.name);  //小猪

         console.log(person2.name);  //小花

 

 

代码二

         Person.prototype.name = '小猪';  //①

         function Person(){}

         var person1 = new Person();   //这里通过构造函数构造对象,这里的原型在①处

         Person.prototype.name = '小花';    //③  这是在①对象的属性上改变属性值

         Person.prototype = {   //② 

             name:'小龙'        //这是一个新的原型对象,和①没有任何关系

         }

         console.log(person1.name);  //小花

 

代码三

 

         Person.prototype.name = '小猪';  //①

         function Person(){}

         Person.prototype = {   //② 

             name:'小龙'        //  这是在构造对象之前改的原型,虽说是一个新对象,因为与①处的重名,会把①处的覆盖

         }

         var person1 = new Person();   //这里通过构造函数构造对象,这里的原型在②处

         console.log(person1.name);  //小龙

 

 

  1. 其他补充

(1) 原型也是对象,数组也是对象,函数也是对象

(2) 在开发时,如果你的一个属性不想被别人用或者修改,可以在属性名前加一个下划线_,如_demo,表示私人的

 

(二)原型链

  1. 概念:把各个原型连在一起,就形成了原型链,连接点:__proto__
  2. 代码说明

 

     Grand.prototype.lastName = '张';

        function Grand(){

            this.name = '国庆';

        }

        var grand = new Grand();

        Father.prototype = grand;

        function Father(){

            this.name = '改革';

        }

        var father = new Father();

        Son.prototype = father;

        function Son(){

            this.name = '小宝';

        }

        var son = new Son();

        son.lastName = '王';

        console.log(son.lastName);  //王

        console.log(father.lastName);  //张

 

 

  1. Object.prototype是所有对象的最终原型,它上面就没有__proto__
  2. 绝大多数(不是所有)对象最终都会继承自Object.prototype
  3. 这里的绝大多数对象而不是所有对象,是因为有一种创建对象的方法,可以创建出一种不带原型的对象,因为它没有原型,更别说最终原型了,这种方法是Object.create(原型/null)
  4. Object.create(原型/null)(创建对象的第三种方法)

(1) 创建方式

var obj = {

            name:'小李'

        }

        var obj1 = Object.create(obj);

 

(2) 上面第5步说了这个方法可以创建一个没有原型的对象,创建方式就是Object.create(null),虽然它没有原型,但是我们可以手动跟他加原型,但是手动加的原型,系统识别不到,因此也无法访问到其原型上的属性,如

 

 

 

 

(3) __proto__属于系统内部属性,我们可以手动改动他的原型指向,他是绝对不能给一个没有原型的对象,通过它来添加原型

(4) 原型链的增删改查(和原型上的一样)

 

 

 

 

(5) 按理来说,子孙是不能直接修改祖先(原型)上的属性的,但是原型上如果有一个引用值,子孙可以通过调用其引用值修改其引用值中的东西的,具体代码如下

修改前

 

 

 

修改后

 

 

 

 

(6) 原型和this指向问题

下面代码中sayName方法里面的this指向谁呢?

谁调用的这个方法,this就指向谁

 

 Person.prototype = {

            name:'小张',

            sayName:function(){

                console.log(this.name);

            }

        }

        function Person(){

            this.name = '小李';

        }

        var person = new Person();

        console.log(person.sayName());  //'小李'

 

        //sayName里面的this指向是什么,谁调用的sayName的这个方法,this就指向谁

 

 

(7) toString()方法的深层原理

① 我们在讲解toString()方法时,说过undefinednull没有这个方法,而其他的数据类型,如数字,字符串,布尔值就有这个属性呢,原因是undefinednull没有包装类,而数字,字符串,布尔值是通过包装类访问到这个属性的

② 过程

原始值 ——> 包装类 ——> 把自己包装成对象 ——> 访问对象原型上的toString方法

(8) 为什么对象调用toString()返回"[object Object]"而数字或者其他原始值返回的是"数字或其他"呢

① 原因:因为对象调用的这个toString()是终极原型上的,而其他原始值调用的这个toString()是其包装类原型上的

② 我们可以通过call来改变原始值的调用指向,让它调用终极原型上的toString()

 

 

  1. 补充问题

(1) 不要这样调用toString方法(直接数字.toString()),因为系统会把这个点解析为浮点数的点(因为其优先级高)

(2) 方法的重写:如果原型上有这个方法,我们自身也有这个方法,这就叫方法的重写(同样的名,不同的方法,是一种覆盖的方法)

(3) 终极原型下面的原型也有toString()方法,这就是系统自带的方法的重写(选用时采取就近原则)

 

 

 

 

(4) document.write()也是隐式的去调用了toString()方法(下面是验证document.write()是否会调用toString()方法)

 

 

 

(5) Js在处理小数数据时,精度是不准的,所以我们要避免小数操作,如果要使用小数,尽量使用向上取整(Math.ceil() 123.234 --> 124/向下取整(Math.floor() 123.998  --> 123

下面代码是向上取整和向下取整的小demo(0100之间的整数)

 

 

 

(6) Math.random() 01之间的开区间的随机数

(7) toFixed(a)  小数点后保留a

(8) Js可正常计算的范围:小数点前16位,后16

 

(三)call/apply

 

  1. call/apply的作用是改变this指向,区别是传参列表不同
  2. call/apply(改变this指向的对象,参数)
  3. 只有函数才可以调用call/apply方法(函数本身就是一个方法)
  4. 可以把两个方法中重复的属性放在一个方法里面,用call来借用,具体代码如下:

 

  function PersonOne(name,age,sex){

            this.name = name;

            this.age = age;

            this.sex = sex;

        }

        function PersonTwo(name,age,sex,color){

            //var this = { __proto__:PersonTwo.prototype }

            PersonOne.call(this,name,age,sex);

            this.color = color;

        }

        var person1 = new PersonTwo('小李',23,'男','blue');

        console.log(person1);

 

 

  1. call传参:需要把实参按照形参式的个数传进去
  2. apply传参:需要传一个实参列表(arguments)(数组)具体代码如下

 

function PersonOne(name,age,sex){

            this.name = name;

            this.age = age;

            this.sex = sex;

        }

        function PersonTwo(name,age,sex,color){

            //var this = { __proto__:PersonTwo.prototype }

            PersonOne.apply(this,[name,age,sex]);

            this.color = color;

        }

        var person1 = new PersonTwo('小李',23,'男','blue');

        console.log(person1);

 

 

  1. 函数执行的工作原理

 

function  test() {}  

test()  -->  test.call()

 

  1. 其他代码说明

 

call改变this指向

 function Person(color){

            //this == obj

            this.name = '李明';

            this.age = 29;

            this.color = color;

        }

        var person1 = new Person();

        var obj = {};

        // Person.call(obj); //让Person里面预设的this全部变成obj

        Person.call(obj,'red');  //传参

        console.log(obj); //{name: "李明", age: 29, color: "red"}

 

posted @ 2020-03-13 19:10  code~he  阅读(273)  评论(0)    收藏  举报