1

如果没有面向对象这种抽象概念的小伙伴,建议先看一下我写的
JS基础入门篇(三十四)—面向对象(一)????

1.非常非常重要而又简单的概念—原型链

想要以下为 f 添加一个say方法,有三种方法。

<script>
    function Fn(){};
    var f = new Fn();
</script>

方法一:相当于添加一个自定义属性,此属性是一个方法。

<script>
    function Fn(){};
    var f = new Fn();
    f.say = function(){
        console.log(1);
    }
    f.say(); //打印 1
</script>

方法二:为构造函数.prototype添加一个say方法。

<script>
    function Fn(){};
    var f = new Fn();
    Fn.prototype.say = function(){
        console.log(2);
    }
    f.say(); //打印 2
</script>

方法三:为Object.prototype添加一个say方法。

<script>
    function Fn(){};
    var f = new Fn();
    Object.prototype.say = function(){
        console.log(3);
    }
    f.say(); //打印 3
</script>

疑问?️:方法二中挂在构造函数的方法,和方法三中挂在Object的方法, f 为什么能查找的到???

解析(此解析一定要看懂,没有看懂多看几遍或者百度下):
是原型链的概念。就是js内部的查找机制。首先要明白:

1.prototype 原型

    当一个函数被申明的时候,该函数下默认有一个属性:prototype,该属性的值是一个对象。

举例说明:

<script>
    function Fn(){}
    var f = new Fn();
    console.log( Fn.prototype );
</script>

结果如图所示:
图片描述

2.__proto__

    当一个对象被创建的时候,该对象会自动被添加上一个属性:__proto__,他的值也是一个对象,并且该属性 就是 当前这个对象的构造函数的prototype
                            

举例说明:

<script>
    function Fn(){}
    var f = new Fn();
    console.log( f.__proto__ );
</script>

结果如图所示:
图片描述

3.对象.__proto__ === 构造函数.prototype

举例说明

<script>

    function Fn(){};
    Fn.prototype.say=function () {
        console.log(1);
    };
    var f = new CreatePreson();
    f.say=function () {
        console.log(2);
    };
    console.log( f.__proto__  );
    console.log( Fn.prototype );
    console.log( Fn.prototype ===  f.__proto__ );
</script>

结果如图所示:
图片描述

所以查找机制为:
调用f.say( );时

if( 对象 f上面是否say方法 ){//为真,执行if内部的代码
       则调用f上面的say方法
}else if(Fn.prototype是否有say方法){//为真,执行else if内部的代码
  第一步:f.__proto__  === Fn.prototype
  由这个查找到f对应的构造函数的原型,即为 Fn.prototype。
  
  第二步:查看Fn.prototype是否有say方法,有的话,则调用Fn.prototype是上面的say方法。
  
}else if( Object.prototype是否有say方法 ){
  第一步:Fn.prototype.__proto__ === Object.prototype
  由这个查找到Fn.prototype对应的构造函数的原型,即为 Object.prototype。
  
  第二步:Object.prototype是否有say方法,有的话,则调用Object.prototype是上面的say方法。
   
}else{//如果以上都没有say方法 
    会报错。
}

举例说明

<script>
        function Fn() {
        }
        var f = new Fn();
        f.say = function () {
            console.log(1);
        };
        Fn.prototype.say = function () {
            console.log(2);
        };
        Object.prototype.say = function () {
            console.log(3);
        };

        f.say();//打印1。因为在f上面找到了,就不会往下继续找了。
</script>

2.hasOwnProperty, constructor, instanceof

1.hasOwnPropert ???

    作用
        用来判断某个对象是否含有 指定的 自身属性
    语法
        boolean object.hasOwnProperty(prop)
    参数
        object
            要检测的对象
        prop
            要检测的属性名称。
    注意:不会沿着原型链查找属性,只查找自身属性

如果以上文字都看不懂,可以先看例子,再看文字。

<script>
    //创建构造函数
    function CreatPerson(name, age) {
        this.name = name;
        this.age = age;
    }
    CreatPerson.prototype.kind = "人类";
    CreatPerson.prototype.say = function () {
        console.log("我会说话 ");
    };

    //生成对象,实例化的过程
    var p = new CreatPerson("Lily",28);

    //调用hasOwnProperty方法,查看是否是自身的属性,不再在原型链上面找。
    console.log(p.hasOwnProperty("name"));//true
    console.log(p.hasOwnProperty("age"));//true
    console.log(p.hasOwnProperty("kind"));//false
    console.log(p.hasOwnProperty("say"));//false
</script>

2.constructor ???

函数的原型prototype的值是一个对象,初始化会有一个属性为constructor,
        对应的值为拥有这个原型的函数
    注意:prototype的值是可以修改的,修改了prototype的值,
        要手动将constructor指向函数
<script>
    function Fn() {
        console.log("构造函数");
    }
    console.log(Fn.prototype.constructor); // Fn(){console.log("构造函数");
    


    //因为arr 是通过字面量的方式生成一个数组,但是函数内部还是会通过new Array 生成arr对象
    //所以Array是arr的构造函数
    //arr没有constructor,会根据原型链查找,找到JS内部的Array.prototype上的constructor方法。
    //Array.prototype.constructor指向Array
    var arr = [1, 2, 3];
    console.log(arr.constructor); // Array() { [native code] }


    //因为obj 是通过字面量的方式生成一个对象,但是函数内部还是会通过new Object 生成obj对象
    //所以Object是obj的构造函数
    //obj没有constructor,会根据原型链查找,找到JS内部的Object.prototype上的constructor方法。
    //Object.prototype.constructor指向Object
    var obj = {};
    console.log(obj.constructor); //Object() { [native code] }
</script>

3.instanceof???

instanceof
    是一个二元运算符,返回布尔值
运算检测 一个 对象原型 是否 在要检测的对象的原型链上
    使用:object instanceof constructor
<script>
   var arr = [];
    console.log( typeof arr );//"object"
    console.log( arr instanceof Array);//true
    console.log( arr instanceof Object);//true
    
    //str是字面量生成的,是由JS内部的String构造函数new出来的。
    //但是str会立刻"压扁"自己,让自己不是对象。
    //所以str都不是对象了,自然instanceof String 的得到的值为fasle
    //但str.indexOf(),str还是可以调用indexOf()方法的原因是,当它调用方法的时候,会重新将自己包装成对象。
    //使用结束后会重新"压扁"自己,让自己不是对象。
    var str = "123";
    console.log( str instanceof Array );//false
    console.log( str instanceof String);//false
    console.log( str instanceof Object);//false
    
    var obj = {};
    console.log( obj instanceof Array );//false
    console.log( obj instanceof Object);//true
    
    // Array.prototype -> Object.prototype
</script>

3.this的指向

1.谁调用就指向谁。
2.谁触发就指向谁。
举例说明1

 <script>
        function  fn() {
            console.log(this);
        }
        fn();//打印结果:Window     解析:相当于 window.fn(); 所以指向window
        document.onclick=fn;//打印结果:document 解析: 由document的触发,所以指向document
    </script>

举例说明2

<script>
    var obj={
        n:"k",
        foo:function () {
            console.log(this);
            console.log(this.n);
        }
    };

    obj.foo();
    //运行结果为:
    //{n: "k", foo: ƒ}
    //k
    //解析:obj.foo();是obj调用foo对应的函数。所以this指向obj。

    var b = obj.foo;
    b();
    //运行结果为:
    //Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
    //undefined
    //解析:var b = obj.foo; ==== var b=function () { console.log(this); console.log(this.n);}
    //变量b是Window 的自定义属性,所以b(); === window.b();
    //所以其中的this指向window,this.n === window.n
    //由于window上面没有n这个自定义属性,则打印出来为 undefined
    
</script>

4.修改this指向的三种方式

1.call

1. 函数**`会`**立刻执行
2. 函数执行时候,函数**`第一个参数`**是内部的**`this指向`**
3. **`第一个参数之后的参数,都是指 函数执行时候 ,内部的实参`**

直接撸代码,举例说明

 <script>
        function Fn() {
            console.log(this);
        }
          Fn.call();
        //结果为:Window
        // 1.函数会立即执行
        // 2.不传入任何参数,this的指向不变,还是指向window。


          Fn.call(document);
        //结果为:#document
        // 1.函数会立即执行
        // 2.括号中的内容 第一个参数 就是 函数执行时候 ,内部的this指向



        function Go(a,b) {
            console.log(this);
            console.log(a,b);
        }
        Go.call(document,2,3);
        //结果为:
        // #document
        // 2 3

        
        // 1.函数会立即执行
        // 2.括号中的内容 第一个参数 就是 函数执行时候 ,内部的this指向
        // 3.第一个参数之后的参数,都是指 函数执行时候 ,内部的实参
</script>

2.bind

1. 函数**`不会`**立刻执行
2. 函数执行时候,**函数第一个参数是内部的this指向**
3. **第一个参数之后的参数,都是指 函数执行时候 ,内部的实参**
4. **`返回的是 修改了 this指向的新函数`**

与call的区别就是函数不会立刻执行。

举例说明

<script>
    function foo (a,b) {
        console.log( this );
        console.log( a,b );
    }
    var fn = foo.bind( document,2,3);// 函数 不会 立刻执行,返回的是 修改了 this指向的新函数
    fn();//调用之后才会执行 this指向的新函数
    
    //运行结果:
    //#document
    //2 3

</script>

3.apply

与call很相似,只是第二个参数值接受数组

举例说明

<script>
    function foo (a,b) {
        console.log( this );
        console.log( a,b );
    }
    foo.apply( document,[2,3] ); // 和call 相似 直接调用 , 不过第二个参数接受数组
    //运行结果:
    //#document
    //2 3
    
</script>    

5.数组的检测

因为由typeof打印出来,数组和对象的结果都是object。有时候我们需要判断变量是否是数组

方法一:

   var arr = [1,2,3];
    console.log( arr.toString() );//1,2,3
    Array.prototype.toString = Object.prototype.toString;//重新赋值Array.prototype.toString的方法。但是下次在别的情况调用Array.prototype.toString,此方法已被重新覆盖。所以不太好
    console.log( arr.toString() );//[object Array]

var arr = [1,2,3];

console.log( Object.prototype.toString.call(arr) );

// 使用 Object.prototype.toString
// 同时 修改内部的this指向 arr

console.log( arr );//[object Array]

方法二:

6.继承

继承
在JavaScript中,继承就是让一个对象(子类)拥有另一个对象(父类)的属性/方法(还有原型上的属性和方法)。其中原则就是:

1.子类的修改不能影响父类
2.子类可以 在 父类 基础上 添加自己的属性 和 方法

1.通过prototype 赋值 (行不通,但是还是要看行不通的原因)

举例说明:上代码

<script>
    function CreatPerson() {}
    CreatPerson.prototype.say = function () {
        console.log("我会说汉语");
    };

    function Coder(){}
    // 此处 子类 的 prototype和父类的 prototype 指的是 同一个对象。
    // 的确是继承CreatPerson的原型上面的方法,但是当Coder.prototype重写say方法
    // CreatPerson.prototype中的say方法也会被改写
    Coder.prototype = CreatPerson.prototype;
    Coder.prototype.say=function () {//
            console.log("我会说汉语,还会码代码");
    };

    var person = new CreatPerson();
    person.say();
    var coder = new Coder();
    coder.say();


</script>

2.原型链继承

    子类的原型 = 父类的实例
    注意 : 在为 子类 原型 赋值的时候去修正 constructor
    弊端 : 子类构造函数内的地址的修改会修改其他子类。
    因为所有子类构造函数的原型共享一个实例。

举例说明

<script>
        function CreatPerson() {
            this.age=18;
            this.arr=[1,2,3];
        }
        CreatPerson.prototype.say=function () {
            console.log("我会说汉语");
        };
        CreatPerson.prototype.eat=function () {
            console.log("我想吃饭");
        };

        function Coder() {}
        Coder.prototype = new CreatPerson();//子类构造函数内的地址的修改会修改其他子类。因为所有子类构造函数的原型共享一个实例
        Coder.prototype.constructor=Coder;//在为 子类 原型 赋值的时候去修正 constructor
        Coder.prototype.say=function () {
            console.log("我会说汉语,还会码代码");
        };

        var person=new CreatPerson();
        var coder1=new Coder();
        person.say();//我会说汉语
        coder1.say();//我会说汉语,还会码代码
        coder1.eat();//我想吃饭

//----------可以继承父类,修改子类也不会影响到父类。但是子类修改会影响到子类-------------------
        var coder2=new Coder();
        coder2.age=10;
        //coder2.age -> Coder.prototype.age === new CreatPerson().age
        // 存储的是值,Coder.prototype.age的改变,只会影响当前对象的age
        // 别的子类影响不到
        coder2.arr.push(4);
        //coder2.arr -> Coder.prototype.arr === new CreatPerson().arr 
        // 存储的是地址,Coder.prototype.arr 修改,new CreatPerson().arr 取到的内容就是修改后的内容
        
        console.log(coder1.age);//18
        console.log(coder1.arr);//[1, 2, 3, 4]
        console.log(coder2.age);//10
        console.log(coder2.arr);//[1, 2, 3, 4]
        console.log(person.age);//18
        console.log(person.arr);//[1, 2, 3]
</script>

对原型链继承遇到问题的解决的方案一:

改为:

function Coder() {
    this.arr=[1,2,3];//这样查找的时候,对象上面就有了,不会查找到上一层,既不会修改到。
}

解析:此方法麻烦,如果父类有很多自定义属性都是对象或者方法,那么子类都要重新复制一遍。

对原型链继承遇到问题的解决的方案二:

借用构造函数
    在子类中执行父类的构造函数
        修改子类构造函数中的 this指向

只能继承父类构造函数中的方法和属性
    继承不到父类构造函数原型链中的方法和属性

改为:

 function Coder() {
        CreatPerson.call(this);// // 此处的 this 指的 是 Coder 的 实例
    }

总结:通过原型链继承的正确写法。

<script>
        //父类构造函数
        function CreatPerson( name ) {
            this.age = 18;
            this.arr = [123];
            this.name = name;
        }
        CreatPerson.prototype.say = function () {
            console.log("我会说汉语");
        };

        //子类构造函数
        function Corder(name,job) {
            CreatPerson.call(this,name);//继承父类上非原型上的属性和方法。
            this.job=job;//子类扩展的自定义属性
        }
        Corder.prototype=new CreatPerson();//继承父类上原型上的属性和方法。
         Coder.prototype.constructor=Coder;//在为 子类 原型 赋值的时候去修正 constructor
        Corder.prototype.say=function () {//重写父类上面的say方法,并不修改父类的say方法
            console.log("我会说汉语,我是程序员!!!");
        };

        //父类的对象实例化
        console.log("------------ 父类1 -----------");
        var person=new CreatPerson("jack");
        console.log(person.age);//18
        console.log(person.name);//jack


        //子类1的对象实例化
        console.log("------------ 子类1 -----------");
        var corder1=new Corder("rose","worker");
        corder1.arr=[234];
        corder1.say();//我会说汉语,我是程序员!!!
        console.log(corder1.age);//18
        console.log(corder1.arr);//[234]
        console.log(corder1.name);//rose
        console.log(corder1.job);//worker


        //子类2的对象实例化
        console.log("------------ 子类2 -----------");
        var corder2=new Corder("Mary","corder");
        console.log(corder2.age);//18
        console.log(corder2.arr);//[123]子类与子类之间的 自定义属性 没有受到影响
        console.log(corder2.name);//Mary
        console.log(corder2.job);//corder
        corder2.say();//我会说汉语,我是程序员!!!

        console.log("------------ 父类1 -----------");
        person.say();///我会说汉语     父类原型上面的方法 没有受到影响
        console.log(person.arr);//[123]   父类自定义属性 没有受到影响
 </script>

提醒自己: Coder.prototype.constructor=Coder;//在为 子类 原型 赋值的时候去修正 constructor。 不要忘记修正子类的constructor。

3.拷贝式继承

1. 完成拷贝式继承首先要知道如何拷贝对象。所以先来拷贝对象

 <script>

        //拷贝对象的内容
        function cloneFn( sourse ) {
            var obj= (Object.prototype.toString.call(sourse).
            indexOf("Array")!==-1)?[]:{};//如果对象是数组,就应该创建数组。如果是非数组的对象,就应该创建对象。
            for(var attr in sourse){
                if(typeof sourse[attr] ==="object"&& sourse[attr] !==null){//如果对象内的键值还是对象,进行更深一步的拷贝
                    obj[attr]=cloneFn( sourse[attr] );
                }else{
                    obj[attr]=sourse[attr];
                }
            }
            return obj;
        }

        var a={
            abc:1,
            abc2:2,
            arr:[1,23,4]
        };

        var clone=cloneFn( a );
        clone.abc=9;
        clone.arr.push(5);//不会影响a中的arr
        console.log(clone);//{abc: 9, abc2: 2, arr: Array(4)}
        console.log(a.abc);//1
        console.log(a.arr);//[1, 23, 4] 
</script>

2.拷贝继承

<script>
    //拷贝对象的内容
    function cloneFn( sourse ) {
        var obj= (Object.prototype.toString.call(sourse).
        indexOf("Array")!==-1)?[]:{};//如果对象是数组,就应该创建数组。如果是非数组的对象,就应该创建对象。
        for(var attr in sourse){
            if(typeof sourse[attr] ==="object"&& sourse[attr] !==null){//如果对象内的键值还是对象,进行更深一步的拷贝
                obj[attr]=cloneFn( sourse[attr] );
            }else{
                obj[attr]=sourse[attr];
            }
        }
        return obj;
    }
        //父类构造函数
        function CreatPerson( name ) {
            this.age = 18;
            this.arr = [123];
            this.name = name;
        }
        CreatPerson.prototype.say = function () {
            console.log("我会说汉语");
        };

        //子类构造函数
        function Corder(name,job) {
            CreatPerson.call(this,name);//继承父类上非原型上的属性和方法。
            this.job=job;//子类扩展的自定义属性
        }
                       
     
   Corder.prototype=cloneFn(CreatPerson.prototype);//拷贝父类上原型上的属性和方法。
         Coder.prototype.constructor=Coder;//在为 子类 原型 赋值的时候去修正 constructor
        Corder.prototype.say=function () {//重写父类上面的say方法,并不修改父类的say方法
            console.log("我会说汉语,我是程序员!!!");
        };

        //父类的对象实例化
        console.log("------------ 父类1 -----------");
        var person=new CreatPerson("jack");
        console.log(person.age);//18
        console.log(person.name);//jack


        //子类1的对象实例化
        console.log("------------ 子类1 -----------");
        var corder1=new Corder("rose","worker");
        corder1.arr=[234];
        corder1.say();//我会说汉语,我是程序员!!!
        console.log(corder1.age);//18
        console.log(corder1.arr);//[234]
        console.log(corder1.name);//rose
        console.log(corder1.job);//worker


        //子类2的对象实例化
        console.log("------------ 子类2 -----------");
        var corder2=new Corder("Mary","corder");
        console.log(corder2.age);//18
        console.log(corder2.arr);//[123]子类与子类之间的 自定义属性 没有受到影响
        console.log(corder2.name);//Mary
        console.log(corder2.job);//corder
        corder2.say();//我会说汉语,我是程序员!!!

        console.log("------------ 父类1 -----------");
        person.say();///我会说汉语     父类原型上面的方法 没有受到影响
        console.log(person.arr);//[123]   父类自定义属性 没有受到影响
</script>

梁志芳
159 声望58 粉丝

正在学习CSS+HTML+JS