原型与原型链--面向对象、对象、构造函数、原型、原型链、常用的几种继承方式、多态、ES6中的类和对象、深浅拷贝

目录

 一、面向对象思想概述

1.1 面向过程/对象的思想 

1.2 示例演示

二、对象

2.1 概述

2.2 类和对象的关系

三、构造函数

3.1 构造函数创建对象

3.2 静态成员和实例成员

3.3 Function函数

3.4 Object函数

3.5 函数对象关系

四、原型

4.1 概述

 4.2 protype

4.3 原型属性

五、原型链

5.1 概述

六、封装与公私有

6.1 为什么要封装?

6.2 私有属性注意点

6.3  共有/私有的特征

6.4 实现过程

七、常用的几种继承方式

7.1 原型链继承(不常用)

7.2 构造继承

7.3 组合继承(推荐)

7.4 寄生继承(推荐)

八、多态(了解)

8.1 强类型语言、弱类型语言

8.2 什么是多态?

8.3 多态在编程语言中的体现

九、ES6中的类和对象

9.1 概述

9.2 注意点

9.3 继承

9.4 super 关键字调用父类普通函数

9.5 子类继承父类方法同时扩展自己方法

十、深浅拷贝

10.1 概述

10.2 对象的深拷贝


 一、面向对象思想概述

1.1 面向过程/对象的思想 

面向过程(pop)思想:

描述:解决问题围绕(如何解决)来开展编程,核心就是找到解决问题的方法(面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了 )

面向对象(oop)思想:

描述:解决问题围绕(谁能解决)来开展编程,核心就是找到解决问题的对象(面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作 )

区别:

​​​​​​​

 

  •  a.面向过程是亲历亲为,面面俱到,步步紧跟
  •  b.面向对象是找到一个对象,指挥 得结果

  •  c.面向对象是将执行者转变为指挥者 

  •  d.面向对象不是面向过程的替代,而是对它的封装


1.2 示例演示

求两个数相加的和

1.pop:

  • 需将两个变量保存两个数
  • 需要一个算式计算求和
  • 需要一个变量保存结果
  • 需要一个打印语句输出结果
 <script>
        var a = 1;
        var b = 2;
        var sum = a + b;
        console.log(sum); //3
</script>

2.oop:

  • 需要两个变量保存两个数
  • 需要创建一个对象
  • 对象中需要一个能计算加和的方法
  • 对象中这个方法需要能打印计算结果
  • 调用对象执行这个方法
 <script>
        var a = 1;
        var b = 2;
        var obj = {
            sum: function(num1, num2) {
                console.log(num1 + num2);
            }
        }
        obj.sum(a, b);
</script>


二、对象

2.1 概述

关于类和对象,其实万物皆对象。对象是一个具体的事物,比如一本书、一辆汽车、一个人...

类是ES6中新增的,代指的是一个范畴,比如人类,这个大的类,其下又可以分为男人、女人等

例子:

<script>
        // 对象
        var beixi = {
            userName: '贾先生',
            userAge: 18,
            ability: function() {
                console.log('吃了睡睡了吃');
            }
        };

        // 对象
        var lishishi = {
            userName: '李思思',
            userAge: 22,
            ability: function() {
                console.log('卖萌');
            }
        }
</script>

关于对象的更多知识请参考:js-对象


2.2 类和对象的关系

类和对象

    基本描述:在javascript中,类和对象的关系与现实中略有不同,可以说在编程世界中类和对象的关系,是现实生活中类和对象关系的高度抽象。

     说明:

            今天提到的对象,是从面向对象思想中延展出来的产物

            面向对象思想对象-----类中的一个个体

    结论:

     类是一种抽象的,并不实际存在的,表示一种事物共有特征的描述。

     对象是一种具体的,实际存在的,类中的某一个个体。

    注意:

        javascript是一个典型的(面向过程思想)的语言,语言中根本不存在类和对象的概念,但是因为javascript经常要解决(面向对象思想)相关的问题;所以,javascript使用一些手段来模拟面向对象思想的特征,比如类和对象。


三、构造函数

在开始之前我们先来看一个简单的例子,看看它是不是一个类:

 <script>
        function Car() {
            var car = {};
            car.lun1 = '左前轮';
            car.lun2 = '右前轮';
            car.lun3 = '左后轮';
            car.lun4 = '右后轮';
            car.ability = '能跑';
            return car;
        }
        var mycar1 = Car();
        var mycar2 = Car();
        console.log(mycar1);
        console.log(mycar2);
</script>

解析:

  1. 上述代码中声明了一个函数,函数的作用就是返回一个【具有五个属性】的对象。然后我们通过调用函数的方式创建了两个对象。
  2. 但是我们在实际打印这两个对象的时候就能够发现,通过这个函数创建的对象其属性值都是一样的。所有通过这个函数创建的对象都会拥有相同的属性值。
  3. 所以,虽然形式上满足了我们对类的需求(能够创建对象);但是,却并不能满足我们对于类的功能上的需求(创建属性值不同的对象)
  4. 因此,很显然不符合,因此它并不是一个类。

类的概念:

说得通俗易懂点,其实类就是【具有某种特征的函数】


3.1 构造函数创建对象

  • 描述:js中通过构造函数的帮助制定类。通过(new命令调用构造函数)创建类中的对象 

语法:

  function 类名 (对象属性值1,对象属性值2,…) {
     this.属性1 = 对象属性值1;
     this.属性2 = 对象属性值2;
     …
     }

var 对象 = new 类名(value1,value2….)  //这是通过构造函数创建对象的方法

例子:

         function Person(myName,myAge) {
            // let obj=new Object(); 系统自动添加的
            // let this = obj; 系统自动添加的
           this.name=myName;
           this.age=myAge;
            // return this; 系统自动添加的
        }

        Person.prototype = {
        		currentType:"人",
            say:function () {
                console.log("hi");
            }
        }
        /*
        *当 new Person("zs",14); 时系统做了什么?
        * 1.会在构造函数中自动创建一个对象
        * 2.会自动将刚才创建的对象赋值给this
        * 3.会在构造函数最后添加 return this;
        */
        let obj1=new Person("zs",14);
        let obj2=new Person("li",18);
        console.log(obj1.say===obj2.say);//true

执行过程:

  • 开辟新的空间
  • this指向这个对象
  • 执行构造函数里面的代码
  • 返回该对象

instanceof

instanceof:用于哦按段一个对象是否是另一个构造函数的实例对象

语法:对象 instanceof 构造函数

        function A(){}
        function B(){}

        let obj = new B();
        console.log(obj instanceof A); // false
        console.log(obj instanceof B); // true

constructor

constructor:指回构造函数本身

        function A(){}
        function B(){}

        let obj = new B();
        console.log(obj.constructor);


3.2 静态成员和实例成员

  • 在构造函数身上直接添加的成员,称为静态成员(静态属性和静态方法) 
  • 构造函数内部为实例对象准备的成员,称为实例成员(实例属性和实例方法)
  • 实例成员只能由实例化对象访问,静态成员只能由构造函数访问
        // 构造函数中的属性和方法我们称为成员, 成员可以添加
        function Star(uname, age) {
            // 实例属性和实例方法
            this.uname = uname;
            this.age = age;
            this.sing = function() {
                console.log('我会唱歌');

            }
        }

        var ldh = new Star('刘德华', 18);
        // 1.实例成员就是构造函数内部通过this添加的成员 uname age sing 就是实例成员
        // 实例成员只能通过实例化的对象来访问
        console.log(ldh.uname);
        ldh.sing();
        // console.log(Star.uname); // 不可以通过构造函数来访问实例成员

        // 2. 静态成员 在构造函数本身上添加的成员  sex 就是静态成员
        Star.sex = '男';
        // 静态成员只能通过构造函数来访问
        console.log(Star.sex);
        console.log(ldh.sex); // 不能通过对象来访问


3.3 Function函数

  • JavaScript中函数是引用类型(对象类型), 既然是对象, 所以也是通过构造函数创建出来的,"所有函数"都是通过Function构造函数创建出来的对象

  • JavaScript中只要是"函数"就有prototype属性 "Function函数"的prototype属性指向"Function原型对象"

  • JavaScript中只要"原型对象"就有constructor属性 "Function原型对象"的constructor指向它对应的构造函数

  • Person构造函数是Function构造函数的实例对象, 所以也有__proto__属性 Person构造函数的__proto__属性指向"Function原型对象"

 

        function Person(myName, myAge) {
            this.name = myName;
            this.age = myAge;
        }
        let obj1 = new Person("lwj", 18);

        console.log(Function);
        console.log(Function.prototype);
        console.log(Function.prototype.constructor);
        console.log(Function === Function.prototype.constructor); // true

        console.log(Person.__proto__);
        console.log(Person.__proto__ === Function.prototype); // true


3.4 Object函数

创建普通对象的构造函数 

1. JavaScript函数是引用类型(对象类型), 所以Function函数也是对象

2."Function构造函数"也是一个对象, 所以也有__proto__属性 "Function构造函数"__proto__属性指向"Function原型对象"

3. JavaScript中还有一个系统提供的构造函数叫做Object 只要是函数都是"Function构造函数"的实例对象

4.只要是对象就有__proto__属性, 所以"Object构造函数"也有__proto__属性 "Object构造函数"的__proto__属性指向创建它那个构造函数的"原型对象"

5.只要是构造函数都有一个默认的属性, 叫做prototype prototype属性保存着一个对象, 这个对象我们称之为"原型对象"

6.只要是原型对象都有一个默认的属性, 叫做constructor constructor指向当前原型对象对应的那个"构造函数"

 

 

 

        function Person(myName, myAge) {
            this.name = myName;
            this.age = myAge;
        }
        let obj1 = new Person("lnj", 34);
        // console.log(Function.__proto__);
        // console.log(Function.__proto__ === Function.prototype); // true

        // console.log(Object);
        // console.log(Object.__proto__);
        // console.log(Object.__proto__ === Function.prototype); // true
        // console.log(Object.prototype);
        // console.log(Object.prototype.constructor);

        // console.log(Object.prototype.constructor === Object); // true
        // console.log(Object.prototype.__proto__); // null


3.5 函数对象关系

Function 函数是所有函数的祖先函数

所有的构造函数都有一个prototype属性, 所有prototype属性都指向自己的原型对象

所有的原型对象都有一个constructor属性, 所有constructor属性都指向自己的构造函数

所有函数都是Function构造函数的实例对象

所有函数都是对象, 包括Function构造函数

所有对象都有__proto__属性

普通对象的__proto__属性指向创建它的那个构造函数对应的"原型对象"

所有对象的__proto__属性最终都会指向"Object原型对象"

"Object原型对象"的__proto__属性指向NULL

 

 

 ​

        function Person(myName, myAge) {
            this.name = myName;
            this.age = myAge;
        }
        let obj1 = new Person("lnj", 34);

        console.log(Function.prototype.__proto__);
        console.log(Person.prototype.__proto__);
        console.log(Function.prototype.__proto__ === Person.prototype.__proto__);
        console.log(Function.prototype.__proto__ === Object.prototype);
        console.log(Person.prototype.__proto__ === Object.prototype);


四、原型

4.1 概述

描述:原型prototype是js为函数提供的一个对象类型的属性。原型归函数所有,不用创建,是默认存在的。

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

说明:向原型中添加的属性和方法,能够被(由类创建的对象)共同拥有

注意:

  • 本属性不需要手动添加,prototype属性自动被所有函数拥有,直接使用即可
  • 利用原型特点和概念,可以提取公有属性     
  • 对象如何查看原型?隐式属性   __proto__        
  • 对象如何查看对象的构造函数?    constructor

本质:本质上原型的存在就是为了给类的对象添加公有的属性

作用:使用原型能够有效节约内存空间,让所有的对象拥有这个属性
 

js中提供了一种机制:

  • 如果是通过类创建的对象,当访问的属性在对象中如果没有找到;则会去【创建对象的类】的原型中查找。如果能找到,也相当于对象拥有这个属性。

例子1:

 <script>
        function Car() {}

        //实例化对象
        var mycar1 = new Car();
        var mycar2 = new Car();

        //公有的属性
        Car.prototype.lun1 = '左前轮';

        // 改变属性值
        mycar1.lun1 = '防滑轮';
        mycar2.lun1 = '赛车轮';

        console.log(mycar1);
        console.log(mycar1.lun1);

        console.log(mycar2);
        console.log(mycar2.lun1);

        var mycar3 = new Car();
        console.log(mycar3.lun1);
</script>

js中类的创建方法:

 语法:

//注意:类名的首字母需要大写  
function 类名(公有属性1,公有属性2,...){
            var 私有属性名1:'私有属性值1',
            var 私有方法1:function(){},

            this.公有属性名1:公有属性值1,
            this.公有属性名2:公有属性值2,
            ....
        }
        类名.prototype.公有属性3=公有属性值3;
        类名.prototype.公有方法3=function(){};

例子2:

<script>
        function People(pname, page, pability) {
            //私有属性
            var secret = '梦';

            //公有属性(大家都有的,但是是内容不相同的公有属性)
            this.pname = pname;
            this.page = page;
            this.pability = pability;

            //特权函数(特权函数实际上也是公有的,只不过特殊之处在于能够读写私有属性)
            this.getSecret = function() {
                return secret;
            }
            this.setSecret = function(newSecret) {
                secret = newSecret;
            };
        }
        //公有属性(大家都有的,而且内容也一样的公有属性)
        People.prototype.eyesNum = 2;
        People.prototype.breathe = function() {
            console.log('我们都要呼吸');
        }

        // 实例化对象
        var beixi = new People('贾先生', 18, function() {
            console.log('敲代码');
        })

        console.log(beixi.pname);
        console.log(beixi.page);
        beixi.pability();
        beixi.setSecret('成为技术大亨');
        console.log(beixi.getSecret());
        console.log(beixi.eyesNum);
        beixi.breathe();

        // 实例化对象
        var shishi = new People('刘诗诗', 22, function() {
            console.log('卖萌');
        })
        console.log(shishi.pname);
        console.log(shishi.page);
        shishi.pability();
        shishi.setSecret('成为影视圈大咖');
        console.log(shishi.getSecret());
        console.log(shishi.eyesNum);
        shishi.breathe();
</script>


 4.2 protype

protype的特点:

  •  存储在protype中的方法可以被对应构造函数创建出来的所有对象共享
  •  protype中除了存储方法外还可以存储属性
  •  如果protype中所存储的方法和属性与构造函数中的方法和属性同名,那么对象在访问的时候访问到的是构造函数中的方法和属性


protype的应用场景:
protype中一般用于存储所有对象都相同的一些属性或方法,如果是对象特有的属性或方法,我们会存储在构造函数中

        function Person(myName, myAge) {
            this.name = myName;
            this.age = myAge;
            this.currentType = "构造函数中的type";
            this.say = function () {
                console.log("构造函数中的say");
            }
        }

        Person.prototype = {
            currentType: "人",
            say: function () {
                console.log("hello world");
            }
        }

        let obj1 = new Person("lwj", 18);
        obj1.say();
        console.log(obj1.currentType);
        
        let obj2 = new Person("zs", 44);
        obj2.say();
        console.log(obj2.currentType);

对象三角关系:
1.每个"构造函数"中都有一个默认的属性, 叫做prototype:prototype属性保存着一个对象, 这个对象我们称之为"原型对象"

2.每个"原型对象"中都有一个默认的属性, 叫做constructor:constructor指向当前原型对象对应的那个"构造函数"

3.通过构造函数创建出来的对象我们称之为"实例对象":每个"实例对象"中都有一个默认的属性, 叫做__proto__(__proto__指向创建它的那个构造函数的"原型对象")

 

        function Person(myName, myAge) {
            this.name = myName;
            this.age = myAge;
        }

        let obj1 = new Person("lwj", 34);

        console.log(Person.prototype);

        console.log(Person.prototype.constructor);
        
        console.log(obj1.__proto__);


4.3 原型属性

结构:原型是一个对象,在原型中通常拥有两个属性:

     (1)构造器(constructor):该属性指向了这个类本身  (指明了原型归哪个类所有),可以手动修改。

     (2)原型指向(__proto__):该属性指向原型本身,提供给通过(类创建的对象)使用。

 <script>
        function People() {}
        console.log(People.prototype);

        var beixi = new People();
        console.log(beixi.__proto__);
        console.log(People.prototype == beixi.__proto__); // true
</script>

作用:原型用来创建类的公有属性和公有方法,为创建对象服务

优点:节约内存空间,不必为每一个对象都分配公有属性和公有方法的内存

 <script>
        function People() {}

        var peo1 = new People();
        var peo2 = new People();

        People.prototype.frineds = ['张三', '李四', '王五', '赵刘'];
        peo1.frineds.pop(); //删除最后一个元素

        console.log(peo1.frineds);
        console.log(peo2.frineds);
</script>

缺点:不能保存数组这类引用类型的数据(地址传递的问题会导致出现修改的连锁变化)


五、原型链

5.1 概述

  • 描述:由【对象的__proto__属性】和【对象的构造函数的原型的__proto__属性】构成的链式结构称为原型链

对象在查找属性和方法的时候, 会先在当前对象查找;如果当前对象中找不到想要的, 会依次去上一级原型对象中查找;如果找到Object原型对象都没有找到, 就会报错

注意:

  • ps:原型链的顶端是Object对象
  • pss:Object对象没有__proto__属性,或者说Object对象的__proto__属性指向了自身

<script>
    function Car() {};
    
    Car.prototype.lun1 = "左前轮";

    //实例化
    var mycar1 = new Car();

    console.log(mycar1 __proto__);
    console.log(mycar1 __proto__.__proto__);
    console.log(mycar1 __proto__.__proto__.__proto__);
</script>

我们来看一个图,应该会更理解一些:

作用:

  1. 访问对象的属性或方法的时候,首先在本身中查找是否拥有这个属性或方法
  2. 如果没有找到那么就沿着原型链向上逐级查找直到Object为止,在任何一级寻找到这个属性或方法都视为对象拥有这个属性或方法(继承)

为了不破坏原有的关系, 在给prototype赋值的时候, 需要在自定义的对象中手动的添加constructor属性, 手动的指定需要指向谁:
        /*
        1.对象中__proto__组成的链条我们称之为原型链
        2.对象在查找属性和方法的时候, 会先在当前对象查找
          如果当前对象中找不到想要的, 会依次去上一级原型对象中查找
          如果找到Object原型对象都没有找到, 就会报错
         */
        function Person(myName, myAge) {
            this.name = myName;
            this.age = myAge;
        }

        Person.prototype = {
            // 注意点: 为了不破坏原有的关系, 在给prototype赋值的时候, 需要在自定义的对象中手动的添加constructor属性, 手动的指定需要指向谁
            constructor: Person,
            // currentType: "人",
            // say: function () {
            //     console.log("hello world");
            // }
        }
        let obj1 = new Person("lnj", 34);
        // obj1.say();
        console.log(obj1.currentType);
        // console.log(Person.prototype.constructor);

在给一个对象的属性设置值的时候,如果构造函数中不存在的时候, 不会去原型对象中查找,也不会把这个值设置给原型对象中的属性,会直接给当前对象新增一个不存在的属性:
        function Person(myName, myAge) {
            this.name = myName;
            this.age = myAge;
        }
        Person.prototype = {
            constructor: Person,
            currentType: "人",
            say: function () {
                console.log("hello world");
            }
        }
        let obj = new Person("lnj", 34);
        // console.log(obj.currentType); // "人"
        // console.log(obj.__proto__.currentType); // "人"

        // 注意点: 在给一个对象不存在的属性设置值的时候, 不会去原型对象中查找, 如果当前对象没有就会给当前对象新增一个不存在的属性
        obj.currentType = "新设置的值";
        console.log(obj.currentType); // 新设置的值
        console.log(obj.__proto__.currentType); // "人"

 构建:设置子类的prototype属性是父类的实例,即可构建原型链;原型链的创建实际上就是将【函数的原型】设置为【另一个函数的对象】

<script>
        function Father() {}
        Father.prototype.fname = "beixi";
        var father = new Father();

        function Son() {}
        Son.prototype = father;
</script>


六、封装与公私有

局部变量和局部函数:

  • 无论是ES6之前还是ES6, 只要定义一个函数就会开启一个新的作用域
  • 只要在这个新的作用域中, 通过let/var定义的变量就是局部变量
  • 只要在这个新的作用域中, 定义的函数就是局部函数

什么是对象的私有变量和函数:

默认情况下对象中的属性和方法都是公有的, 只要拿到对象就能操作对象的属性和方法

外界不能直接访问的变量和函数就是私有变量和是有函数

构造函数的本质也是一个函数, 所以也会开启一个新的作用域, 所以在构造函数中定义的变量和函数就是私有和函数

        function Person() {
            this.name = "lwj";
            // this.age = 34;
            let age = 34;
            this.setAge = function (myAge) {
                if(myAge >= 0){
                    age = myAge;
                }
            }
            this.getAge = function () {
                return age;
            }
            this.say = function () {
                console.log("hello world");
            }
            
            // 由于构造函数也是一个函数, 所以也会开启一个新的作用域
            // 所以在构造函数中通过var/let定义的变量也是局部变量
            // 所以在构造函数中定义的函数也是局部函数
            var num = 123;
            let value = 456;
            function test() {
                console.log("test");
            }
            
        }
        let obj = new Person();
        // 结论: 默认情况下对象的属性和方法都是公开的, 只要拿到对象就可以操作对象的属性和方法
        console.log(obj.name);
        obj.age = -3;
        console.log(obj.age);
        obj.say();

        console.log(age);
        obj.setAge(-3);
        console.log(obj.getAge());

描述:封装是指构造具有某种特征的类,以通过对其进行实例化,来获得满足需求的对象的过程

6.1 为什么要封装?

  • 不封装的缺点:当一个类把自己的成员变量暴露给外部的时候,那么该类就失去对属性的管理权,别人可以任意的修改你的属性
  • 封装就是将数据隐藏起来,只能用此类的方法才可以读取或者设置数据,不可被外部任意修改. 封装是面向对象设计本质(将变化隔离)。这样降低了数据被误用的可能 (提高安全性和灵活性)

6.2 私有属性注意点

  1. 在给一个对象不存在的属性设置值的时候, 不会去原型对象中查找, 如果当前对象没有就会给当前对象新增一个不存在的属性
  2. 由于私有属性的本质就是一个局部变量, 并不是真正的属性, 所以如果通过 对象.xxx的方式是找不到私有属性的, 所以会给当前对象新增一个不存在的属性

6.3  共有/私有的特征

公有:对象中属性和方法,在对象外部能够直接访问,这样的属性和方法就称为公有的属性和方法

私有:对象中的属性和方法,仅在对象内部使用,这样的属性和方法就称为私有的属性和方法

6.4 实现过程

通过在构造函数中,(this.属性)的方式为类添加公有的属性和方法,this.属性所添加的内容在对象的外部能够直接被访问

通过在构造函数中,(添加局部变量和闭包)的方式为类添加私有的属性和方法,局部变量保证了对象外部无法直接获取,闭包保证了对象外部可以间接获取

例子1:

<script>
    function (pname,pability){
        //公有属性和方法
        this.pname = pname;
        this.pability= pability;

        //js局部变量来模拟私有属性和方法
        var secret = "梦";
        
        //特权函数-->一种方法(闭包)
        this.getSecret = function(){
            console.log(secret );
        }
    }

    var beixi = new People('张先生',function(){
            console.log('敲代码');
        });

    //公有属性和方法在外部可以直接访问
    console.log(beixi.pname);
    beixi.pability;

    //私有的属性和方法不能再对象之外直接访问
    //间接访问
    //console.log(beixi.secret); 
    beixi.getSecret();
</script>

结论:

默认情况下对象的属性和方法都是公开的,只要拿到对象,就可以操作对象的属性和方法

由于构造函数也是一个函数, 所以也会开启一个新的作用域,所以在构造函数中通过var/let定义的变量也是局部变量,在构造函数中定义的函数也是局部函数

示例1:

        function Person() {
            this.name = "xx";
            // this.age = 34;
            let age = 34;//私有属性
            //设置
            this.setAge = function (myAge) {
                if(myAge >= 0){
                    age = myAge;
                }
            };
            // 获取
            this.getAge = function () {
                return age;
            }
            //共有方法
            this.say = function () {
                console.log("hello world");
            };
            /*
            // 由于构造函数也是一个函数, 所以也会开启一个新的作用域
            // 所以在构造函数中通过var/let定义的变量也是局部变量
            // 所以在构造函数中定义的函数也是局部函数
            var num = 123;
            let value = 456;
            function test() {
                console.log("test");
            }
            */
        }
        let obj = new Person();
        // 结论: 默认情况下对象的属性和方法都是公开的, 只要拿到对象就可以操作对象的属性和方法
        // console.log(obj.name);
        // obj.age = -3;
        // console.log(obj.age);
        // obj.say();

        // console.log(age);//报错
        obj.setAge(-3);
        console.log(obj.getAge());

例子:2:

<script>
        function People(pname, pability) {
            //公有的属性和方法
            this.pname = pname;
            this.pability = pability;

            //this.eyesNum = eyesNum;

            //js局部变量来模拟私有属性和方法
            var secret = '梦';

            //特权函数-一种方法 相当于一个闭包
            this.getSecret = function() {
                console.log(secret);
            }
        }

        //prototype原型:为了解决js模拟面向对象时,一些(共同拥有的属性值)而出现的解决方法
        People.prototype.eyesNum = 2;
        People.prototype.breahte = function() {
            console.log('呼吸');
        }

        // var beixi = new People('贾先生', function() {
        //     console.log('敲代码');
        // }, 2);
        // var shishi = new People('刘诗诗', function() {
        //     console.log('卖萌');
        // }, 2);

        var beixi = new People('贾先生', function() {
            console.log('敲代码');
        });
        var shishi = new People('刘诗诗', function() {
            console.log('卖萌');
        });

        //公有的属性和方法能够在对象之外直接访问
        console.log(beixi.pname); //贾先生
        beixi.pability(); //敲代码

        //私有的属性和方法不能在对象之外直接访问
        //console.log(beixi.secret);
        beixi.getSecret(); //梦
        console.log(beixi.eyesNum); //2
        console.log(shishi.eyesNum); //2
        beixi.breahte(); //呼吸
        shishi.breahte(); //呼吸
</script>


七、常用的几种继承方式

在企业开发中如果构造函数和构造函数之间的关系是 “is  a” 关系,那么就可以使用继承来优化代码(学生 is a 人)

7.1 原型链继承(不常用)

在面向对象的语言中,继承的概念是:子类能够在主动不声明的情况下,使用父类的属性和方法的性质叫做继承

描述:我们必须想办法通过某种手段来模拟继承;而需要使用到的手段,就是原型链。

设置【子类的原型】是【父类的实例】的操作,被认为是js中的继承

例子:

        //构建原型链
        function Father() {}

        Father.prototype.money = "100000000000.00";

        // 创建一个子类,并将prototype属性赋值为Father类的一个对象
        function Son() {}

        /*
        实例化
        var zs = new Father();
        Son.prototype = zs;//等价于:Son.prototype = new Father();
        */
        Son.prototype=new Father();
        // Son.prototype.constructor=Son;//优化--推荐

        // 实例化
        var beixi = new Son();

        console.log(beixi.money);

弊端:

  1. 子类实例化时不能向父类构造函数传参,但是可以直接访问父类的原型属性和实例化属性
  2. 子类的prototype的constructor属性,实际上是父类prototype的constructor属性

<script>
        function Father(fname, fage) {
            this.fname = fname;
            this.fage = fage;
        }

        // 父类的原型属性
        Father.prototype.childNumber = 0;

        function Son() {}

        Son.prototype = new Father("大明", "父亲的年龄");

        var xiaoming = new Son();
        var xiaozhuang = new Son();

        console.log(xiaoming.fname);
        console.log(xiaozhuang.fname);

        console.log(xiaoming.childNumber);
        console.log(xiaozhuang.childNumber);

        console.log(xiaoming.__proto__.constructor);
</script>


7.2 构造继承

弊端:只能访问到父类实例属性,实例化时可以像父类的构造参数传参,但是不能访问父类的原型链和实例属性

 <script>
        function Father(fname, fage) {
            this.fname = fname;
            this.fage = fage;
        }

        // 父类的原型属性
        Father.prototype.childNumber = 0;

        function Son(sname, sage) {
            Father.call(this, sname, sage);
        }

        var xiaoming = new Son('大明', '40');
        var xiaozhuang = new Son('大壮', '33');

        console.log(xiaoming.fname);
        console.log(xiaozhuang.fname);

        console.log(xiaoming.childNumber);
        console.log(xiaozhuang.childNumber);

        console.log(xiaoming.__proto__.constructor);
</script>


7.3 组合继承(推荐)

 不太致命的弊端:

  • 子类子类中设计了两次构造函数
  • 一次构造继承调用,一次链式继承调用;对实例属性共计初始化了两次

        //组合继承
        function Father(fname, fage) {
            this.fname = fname;
            this.fage = fage;
        }

        Father.prototype.childNumber = 0;

        //构造继承  在子类的构造函数中通过call借助父类的构造函数
        function Son(sname, sage) {
            Father.call(this, sname, sage);
        }

        // 原型链继承  将子类的原型修改为父类的实例对象
        Son.prototype = new Father(); 

        //或:由于子类和父类的原型对象是同一个,所以给子类的元素添加方法,父类也会新增该方法
        // Son.prototype =  Father.prototype;//不推荐


        //子类的prototype的constructor属性,是父类prototype的constructor属性
        Son.prototype.constructor = Son;

        var xiaoming = new Son('大明', '40');

        console.log(xiaoming);
        console.log(xiaoming.childNumber);
        console.log(xiaoming.__proto__.constructor);


7.4 寄生继承(推荐)

  核心思想:

  • 通过寄生方式,砍掉父类的实例属性,这样就能在调用两次父类的构造的时候,不会再次实例方法/属性
<script>
        //组合继承 
        function Father(fname, fage) {
            this.fname = fname;
            this.fage = fage;
        }

        Father.prototype.childNumber = 0;

        //构造继承
        function Son(sname, sage) {
            Father.call(this, sname, sage);
        }

        // 原型链继承
        //Son.prototype = new Father();

        (function() {
            function Super() {}
            Super.prototype = Father.prototype;
            Son.prototype = new Super();
        }());

        //子类的prototype的constructor属性,是父类prototype的constructor属性
        Son.prototype.constructor = Son;

        var xiaoming = new Son('大明', '40');

        console.log(xiaoming);
        console.log(xiaoming.childNumber);
        console.log(xiaoming.__proto__.constructor);
</script>


八、多态(了解)

8.1 强类型语言、弱类型语言

1.1什么是强类型语言:

一般编译型语言都是强类型语言,

强类型语言,要求变量的使用要严格符合定义

例如定义 int num; 那么num中将来就只能够存储整型数据

1.2什么是弱类型语言:

一般解释型语言都是弱类型语言,

弱类型语言, 不会要求变量的使用要严格符合定义

例如定义 let num; num中既可以存储整型, 也可以存储布尔类型等

1.3由于js语言是弱类型的语言, 所以我们不用关注多态


8.2 什么是多态?

多态是指事物的多种状态

例如:

按下 F1 键这个动作,

如果当前在 webstorm 界面下弹出的就是 webstorm 的帮助文档;

如果当前在 Word 下弹出的就是 Word 帮助;

同一个事件发生在不同的对象上会产生不同的结果。


8.3 多态在编程语言中的体现

父类型变量保存子类型对象, 父类型变量当前保存的对象不同, 产生的结果也不同

        function Dog() {
            this.eat = function () {
                console.log(" 狗吃东西");
            }
        }
        
        function Cat() {
            this.eat = function () {
                console.log(" 猫吃东西");
            }
        }
      
        function feed(animal){
            animal.eat();
        }

        let dog = new Dog();
        feed(dog);

        let cat = new Cat();
        feed(cat);


九、ES6中的类和对象

9.1 概述

从ES6开始系统提供了一个名称叫做class的关键字, 这个关键字就是专门用于定义类的

静态属性/静态方法

在企业开发中通过构造函数访问的属性, 我们就称之为静态属性

在企业开发中通过构造函数调用的方法, 我们就称之为静态方法

实例属性/实例方法 :

在企业开发中通过实例对象访问的属性, 我们就称之为实例属性

在企业开发中通过实例对象调用的方法, 我们就称之为实例方法

constructor 构造函数:

constructor() 是类的构造函数(默认方法),用于传递参数,返回实例对象,通过 new 关键字生成对象实例时,会自动调用该方法。如果没有显示定义,类内部会自动给我们创建一个 constructor()

        class Person{
            // 当我们通过new创建对象的时候, 系统会自动调用constructor
            // constructor我们称之为构造函数
            constructor(myName, myAge){
                this.name = myName;
                this.age = myAge;
            }
            // 实例属性
            // name = "lnj";
            // age = 34;
            // 实例方法
            say(){
                console.log(this.name, this.age);
            }
            // 静态属性
            static num = 666;
            // 静态方法
            static run() {
                console.log("run");
            }
        }
        // let p = new Person();

         //静态属性和方法必须通过类名来调用
        let p = new Person("zs", 18);
        p.say();
        console.log(Person.num);
        Person.run();


9.2 注意点

a.以下定义"实例属性"的方式并不是ES6正式版标准中的写法, 大部分的浏览器不支持

        class Person{
            // 以下定义"实例属性"的方式并不是ES6正式版标准中的写法, 大部分的浏览器不支持
            // 在ES6标准中添加实例属性都需要在constructor中添加
            // 实例属性
             name = "lnj";
             age = 34;

            //标准写法
            //constructor(){
            //    this.name = "lnj";
            //   this.age = 34;
            //}

            // 实例方法
            say(){
                console.log(this.name, this.age);
            }
        }
        let p = new Person();
        console.log(p);

b.以下定义"静态属性"的方式并不是ES6正式版标准中的写法, 大部分的浏览器不支持

        class Person{
            // 以下定义"静态属性"的方式并不是ES6正式版标准中的写法, 大部分的浏览器不支持
            // 在ES标准中static只支持定义静态方法不支持定义静态变量
            // 静态属性
            // static num = 666;//大部分浏览器不支持

            // 静态方法
            static run() {
                console.log("run");
            }
        }
        Person.num = 666;//解决措施
        let p = new Person();
        console.log(p);

c.在constructor里添加的属性或方法就等同于在构造函数中添加的属性或方法;在constructor外添加的方法或属性就等价于在构造函数的原型上添加的方法或属性

        class Person{
            constructor(myName, myAge){
                this.name = myName;
                this.age = myAge;
                this.hi = function () {
                    console.log("hi");
                }
            }
            //自动默认添加到原型上
            say(){
                console.log("hi");
            }
        }
        let p = new Person("lnj", 34);
        console.log(p);

d.如果通过class定义类, 那么不能自定义这个类的原型对象 ;如果想将属性和方法保存到原型中, 只能动态给原型对象添加属性和方法

        class Person{
            constructor(myName, myAge){
                this.name = myName;
                this.age = myAge;
                this.hi = function () {
                    console.log("hi");
                }
            }
            run(){
                console.log("run");
            }
        }
        Person.prototype.type = "人";
        Person.prototype.say = function () {
            console.log(this.name, this.age);
        };

      
        let p = new Person("lnj", 34);
        console.log(p);


9.3 继承

ES6之前的继承:

  • 1.在子类中通过call/apply方法借助父类的构造函数
  • 2.将子类的原型对象设置为父类的实例对象
        function Person(myName, myAge) {
            this.name = myName;
            this.age = myAge;
        }
        Person.prototype.say =  function () {
            console.log(this.name, this.age);
        }
        function Student(myName, myAge, myScore) {
            // 1.在子类中通过call/apply方法借助父类的构造函数
            Person.call(this, myName, myAge);
            this.score = myScore;
            this.study = function () {
                console.log("day day up");
            }
        }
        // 2.将子类的原型对象设置为父类的实例对象
        Student.prototype = new Person();
        //子类的prototype的constructor属性,是父类prototype的constructor属性
        Student.prototype.constructor = Student;
        //这样就可以访问父类的属性和方法了

        let stu = new Student("zs", 18, 99);
        stu.say();

ES6继承:

 在ES6中如何继承:

  • 在子类后面添加extends并指定父类的名称
  • 在子类的constructor构造函数中通过super方法借助父类的构造函数

        class Person {
            constructor(myName, myAge) {
                // this = stu;
                this.name = myName; // stu.name = myName;
                this.age = myAge; // stu.age = myAge;
            }
            say() {
                console.log(this.name, this.age);
            }
        }
       
        // 以下代码的含义: 告诉浏览器将来Student这个类需要继承于Person这个类
        class Student extends Person {
            constructor(myName, myAge, myScore) {
                // 1.在子类中通过call/apply方法借助父类的构造函数
                // Person.call(this, myName, myAge);
                super(myName, myAge);
                this.score = myScore;
            }
            study() {
                console.log("day day up");
            }
        }
        let stu = new Student("zs", 18, 98);
        stu.say(); //zs 18


9.4 super 关键字调用父类普通函数

继承中的属性或者方法查找原则: 就近原则

继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的

继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则)

       class Father {
            say() {
                return '我是爸爸';
            }
        }
        class Son extends Father {
            say() {
                // console.log('我是儿子');
                console.log(super.say() + '的儿子');
                // super.say() 就是调用父类中的普通函数 say()
            }
        }
        var son = new Son();
        son.say();


9.5 子类继承父类方法同时扩展自己方法

        // 父类有加法方法
        class Father {
            constructor(x, y) {
                this.x = x;
                this.y = y;
            }
            sum() {
                console.log(this.x + this.y);
            }
        }
        // 子类继承父类加法方法 同时 扩展减法方法
        class Son extends Father {
            constructor(x, y) {
                // 利用super 调用父类的构造函数
                // super 必须在子类this之前调用
                super(x, y);
                this.x = x;
                this.y = y;

            }
            subtract() {
                console.log(this.x - this.y);

            }
        }
        var son = new Son(5, 3);
        son.subtract();
        son.sum();


十、深浅拷贝

10.1 概述

深拷贝:

修改新变量的值不会影响原有变量的值

默认情况下基本数据类型都是深拷贝

        let num1 = 123;
        let num2 = num1;
        num2 = 666; // 修改形变量的值
        console.log(num1);
        console.log(num2);

浅拷贝:

修改新变量的值会影响原有的变量的值

默认情况下引用类型都是浅拷贝

        class Person {
            name = "xxx";
            age = 34;
        }
        let p1 = new Person();
        let p2 = p1;
        p2.name = "zs"; // 修改变量的值
        console.log(p1.name);
        console.log(p2.name);

	let obj = {
			uname : '张三丰',
			age : 22,
			sex : '男',
			color : ['red', 'blue', 'yellow', 'pink'],
			message : {
				index : 1,
				score : 99
			}
		}

		let newObj = {};

		// Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象
		// Object.assign(target, ...sources)    【target:目标对象】,【souce:源对象(可多个)】
		Object.assign(newObj, obj);

		console.log( obj, newObj );

更多关于 object.assign() 的用法可参考:object.assign()


10.2 对象的深拷贝

注意点: 只有被拷贝对象中所有属性都是基本数据类型, 以上代码才是深拷贝

10.2.1 对于基本数据类型

        class Person {
            name = "xxx";
            age = 34;
        }
        let p1 = new Person();
        // 浅拷贝
        // let p2 = p1;
        // p2.name = "zs"; // 修改变量的值


        //创建一个空对象
        let p2 = new Object();

        //方式1
        /*
        p2.name = p1.name;
        p2.age = p1.age;
        p2.name = "zs";
        */

        //方式2
        /*
        for (let key in p1) {
            p2[key] = p1[key];
        }
        console.log(p2);
        p2.name = "zs";
        */

        //方式3
        // assign方法可以将第二个参数的对象的属性和方法拷贝到第一个参数的对象中
        Object.assign(p2, p1);
        console.log(p2);
        p2.name = "zs";
        console.log(p1.name);
        console.log(p2.name);


10.2.2 对于含引用数据类型

        class Person {
            name = "xxx";
            cat = {
                age: 3
            };
            scores = [1, 3, 5];
        }
        let p1 = new Person();
        let p2 = new Object();

        class Person {
            name = "xxx";
            cat = {
                age: 3
            };
            scores = [1, 3, 5];
        }
        let p1 = new Person();
        let p2 = new Object();


        p2.name = p1.name;
        p2.name = "zs";
        console.log(p1.name);
        console.log(p2.name);
        p2.cat = p1.cat;
        p2.cat.age = 666;
        console.log(p1.cat.age);
        console.log(p2.cat.age);


10.2.3 终极方案1(推荐)

        class Person {
            name = "xxx";
            cat = {
                age: 3
            };
            scores = [1, 3, 5];
        }
        let p1 = new Person();
        let p2 = new Object();


        depCopy(p2, p1);
        console.log(p2);

        p2.cat.age = 666;
        console.log(p1.cat.age);
        console.log(p2.cat.age);

        function depCopy(target, source) {
            // 1.通过遍历拿到source中所有的属性
            for (let key in source) {
                // console.log(key);
                // 2.取出当前遍历到的属性对应的取值
                let sourceValue = source[key];
                // console.log(sourceValue);
                // 3.判断当前的取值是否是引用数据类型
                if (sourceValue instanceof Object) {
                    // console.log(sourceValue.constructor);
                    //.constructor拿到对象的构造函数,进而判断是数组还是对象

                    // console.log(new sourceValue.constructor);
                    // new sourceValue.constructor 创建对象或数组 
                    let subTarget = new sourceValue.constructor;

                    target[key] = subTarget;

                    //再次拷贝,将当前的对象或数组以及其取值拷贝进去
                    depCopy(subTarget, sourceValue);
                } else {
                    target[key] = sourceValue;
                }
            }
        }

10.2.4 终极方案2

      let obj = {
        uname: "张三丰",
        age: 22,
        sex: "男",
        color: ["red", "blue", "yellow", "pink"],
        message: {
          score: 99,
          index: 6,
        },
      };

      let newObj = {};

      // 遍历
      // 如果遇到obj[key]是复杂类型,再遍历操作
      function kaobei(newObj, obj) {
        for (let key in obj) {
          if (obj[key] instanceof Array) {
            // obj[key] 是数组
            // 保证newObj[key]是数组
            newObj[key] = [];

            kaobei(newObj[key], obj[key]);
          } else if (obj[key] instanceof Object) {
            // obj[key] 是对象
            // 保证newObj[key]是对象
            newObj[key] = {};

            kaobei(newObj[key], obj[key]);
          } else {
            newObj[key] = obj[key];
          }
        }
      }

      kaobei(newObj, obj);

      obj.message.index = 666;
	  newObj.uname = '王五';

      console.log(obj, newObj);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白小白从不日白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值