zoukankan      html  css  js  c++  java
  • 面向对象的概念,创建,实例,call与apply,继承(js)

    1、面向对象的概念

    面向对象基本概念:

    :类是对象的类型模板,例如,定义Student类来表示学生,类本身是一种类型,Student表示学生类型,但不表示任何具体的某个学生

    实例:实例是根据类创建的对象,例如,根据Student类可以创建出小明、小花等多个实例,每个实例表示一个具体的学生,他们全都属于Student类型。

    面向对象编程,就是编写类(模具),让类产生实例(月饼)

     

    对象格式:无序的名值对

    {
        key1: value1,
        key2: value2,
        ...
    }
     

    对象组成:

    • 属性:对象的特征描述,静态,名词(不是函数的就是属性)

    • 方法:对象的行为,动态(就是函数)

    var obj = {
        name: '小邓',
        age: 3,
        fn: function () {
            console.log(this);
            console.log(this.name);
        }
    };
    ​
    // ------------------------------------
    //
    console.log(obj.name);
    console.log(obj['name']); // 中括号比点的形式更强大,它还可以接收变量
    ​
    obj.fn(); // 函数这样调用,fn中的this就是obj
    // --------------
    var v = obj.fn;
    v(); // window  函数中的this不是在定义的时候确定的,而是在调用的时候确定的
    //
    obj.name = '隔壁老王'; // 如果原来已有这个属性,就会覆盖这个属性
    obj.sex = '男'; // 如果原来没有这个属性,则会增加这个属性
    console.log(obj);
    
    var obj = {
        name: '小王',
        age: 3,
        aa: null,
        bb: undefined,
        fn: function () {
            console.log(this.name);
        }
    };
    ​
    // 当我们试图访问一个不存在的属性时,会返回undefined。
    console.log(obj.sex); // undefined
    // 我们也可以使用in操作符来判断属性是否存在。注意,只是判断是否存在,不是问值是多少
    console.log('sex' in obj); // false
    console.log('age' in obj); // true
    console.log('aa' in obj); // true
    console.log('bb' in obj); // true
    // 我们也可以通过delete操作来删除某个属性。
    delete obj.aa;
    console.log(obj);

    遍历:

    • for-in

    for (var attr in obj) {
        console.log(attr, obj[attr]);
    }

    2、面向对象的创建

    1、字面量创建

    var obj1 = {
        name: '小美',
        age: 3,
        fn: function () {
            console.log('妹妹');
        }
    }

    这种创建方式,适用于单个对象的创建,如果要创建多个对象,会代码冗余。

    2、实例创建

    var obj = new Object();
    obj.name = '小张';
    obj.age = 3;
    obj.fn = function () {
        console.log(this.name);
    }
    console.log(obj);
    ​
    // 调用方式1
    obj.fn();
    ​
    // 调用方式2
    var v = obj.fn;
    v();
     

    如果要创建多个对象,会代码冗余。

    3、工厂模式创建

    // 工厂模式,本质上是封装函数
    function person(name, age) {
        // 1、准备原料
        var obj = new Object();
    ​
        // 2、加工:给对象添加各种属性和方法
        obj.name = name;
        obj.age = age;
        obj.fn = function () {
            console.log('前端开发');
        }
    ​
        // 3、出厂
        return obj;
    }
    var p1 = person('小邓', 3);
    console.log(p1);
    ​
    var p2 = person('小马', 1);
    console.log(p2);

    instanceof

    // 实例 instanceof 类;如果这个实例是由这个类创建的,则返回true
    console.log([] instanceof Array); // true
    console.log([] instanceof Object); // true
    // 工厂模式的不足,不能确定实例是由谁创建的
    console.log(p1 instanceof person); // false
    console.log(p1 instanceof Object); // true

    4、构造函数创建对象

    构造函数的特点:

    • 构造函数名首字母大写(为了区分普通函数,不是必须,是约定)

    • 构造函数方法没有显示的创建对象(new Object())

    • 直接将属性和方法赋值给this对象

    • 没有return语句,不需要返回对象

    • 通过构造函数创建对象,必须使用new运算符(直接调用跟普通函数一样)

    function Person(name, age) {
        this.name = name;
        this.age = age;
        this.fn = function () {
            console.log('前端开发');
        }
    }
    
    // 用new Person()去调用时,会经历以下四个步骤
    // (1) 创建一个新对象;
    // (2) 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象);
    // (3) 执行构造函数中的代码(为这个新对象添加属性);
    // (4) 返回新对象。
    
    var p1 = new Person('小邓', 3);
    console.log(p1);
    
    var p2 = new Person('小马', 1);
    console.log(p2);
    
    // 构造函数解决了识别问题  instanceof
    console.log(p1 instanceof Person); // true
    console.log(p1 instanceof Object); // true
    
    // ------------------------------
    // 构造函数的问题:同一个函数,会创建多次,占内存
    alert(p1.fn);
    alert(p2.fn);
    alert(p1.fn == p2.fn); // false 引用类型比较的是地址,它的地址不一样

    5、原型创建对象

    原型的概念:

    // 每个对象都会有自己的原型对象,是这类对象共有的方法和属性的集合。如Array对象的原型对象中定义了很多的方法和属性,所有的数组都能使用这些方法和属性。
    var arr = []; // 实例
    console.log(arr.__proto__); // 数组的原型
    
    console.log(Array.prototype); // 数组的原型
    console.log(arr.__proto__ == Array.prototype); // true

    原型创建对象

    function Person() { }
    
    // 每创建一个函数,这个函数就有一个prototype属性,这个属性就是原型对象
    // 这个原型对象是js默认给我们加的,原型对象有一个constructor属性,它又指向构造函数
    // console.log(Person.prototype); // {constructor: prototype}
    // console.log(Person.prototype.constructor); // Person 
    
    // 给原型添加属性
    Person.prototype.name = '小张';
    Person.prototype.age = 3;
    Person.prototype.fn = function () {
        console.log('前端开发');
    }
    
    // 创建对象
    var p1 = new Person();
    console.log(p1);
    console.log(p1.__proto__);
    console.log(p1.name); // 小张
    console.log(p1.age); // 3
    console.log(p1.fn);
    
    var p2 = new Person();
    console.log(p2.name);
    console.log(p2.age);
    console.log(p2.fn);
    
    // 原型创建对象,解决了构造函数创建对象时重复创建方法的不足
    console.log(p1.fn == p2.fn); // true
    
    // -----------------
    // 原型创建对象的不足:不能传参
    • 原型:js每声明一个function,都有prototype原型,prototype原型是函数的一个默认属性,在函数的创建过程中由js编译器自动添加。也就是说:当生产一个function对象的时候,就有一个原型prototype。原型中存储对象共享的属性和方法。

    • 原型链: 当你定义一个函数对象的时候,其内部就有这样一个链表关系。声明p1对象,自带了proto的属性,而这个属性指向了prototype,从而实现对象的扩展(例如继承等操作)

    6、混合模式创建 (构造+原型)

    是创建对象的标准模式

    // 混合模式创建 (构造+原型)   (标准模式)
    // 公共的写在原型上面,只有自己独有的写在构造函数里面
    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    Person.prototype.fn = function () {
        console.log('前端开发');
    }
    
    var p1 = new Person('小王', 3);
    console.log(p1);
    
    var p2 = new Person('小李', 2);
    console.log(p2);
    
    

    7、动态混合模式

    只是同混合模式的写法稍稍有点区别

    function Person(name, age) {
        this.name = name;
        this.age = age;
        if (typeof (Person.prototype.fn) !== 'function') {
            Person.prototype.fn = function () {
                console.log('前端开发');
            }
            Person.prototype.showName = function () {
                console.log(this.name);
            }
        }
    }
    
    var p1 = new Person('小王', 3);
    console.log(p1);
    
    var p2 = new Person('小李', 2);
    console.log(p2);

    3、面向对象的实例

    1、面向对象创建飞机

    function Plane(parent, src, x, y) {
        this.parent = parent; // 父级,即图片放的盒子
        this.imgNode = null; // 图片
        this.src = src; // 图片的地址
        this.x = x; // x位置
        this.y = y; // y位置
    }
    Plane.prototype.create = function () {
        this.imgNode = document.createElement('img');
        this.imgNode.src = this.src;
        this.imgNode.style.left = this.x + 'px';
        this.imgNode.style.top = this.y + 'px';
        this.parent.appendChild(this.imgNode);
    }
    Plane.prototype.del = function () {
        this.imgNode.onclick = function () {
            this.parentNode.removeChild(this);
        }
    }
    
    var box = document.getElementById('box');
    setInterval(function () {
        var p1 = new Plane(box, 'img/plane.gif', getRandom(0, 434), getRandom(0, 520));
        p1.create();
        p1.del();
    }, 1000);
    
    function getRandom(min, max) {
        return Math.floor(Math.random() * (max - min + 1) + min);
    }

    2、面向对象的选项卡

    原则:先写出普通的写法,然后改成面向对象写法

    1、普通方法变型

    尽量不要出现函数嵌套函数

    可以有全局变量

    把onload中不是赋值的语句放到单独函数中(init)

    2、改成面向对象()

    先写构造函数

    onload中创建对象,并init调用

    全局变量就是属性

    函数就是方法

    (属性和方法前面,都要加this)

    改this指向问题(尽量让this指向对象)

     

    注意:this指向是这里的重点

    演示时先把所有的this指向改了,然后再看报错,一步一步改。要一步一步分析this都指向谁了。

    在事件或者定时器调用时,this指向特别容易出错。一定要注意(复习this的指向)

    另外,还要注意event对象,它只能出现在事件函数里面。

    阻止默认事件,也要出现在事件函数中。

     

    结论:我们改成面向对象了之后,感觉是不是更复杂了?确实是这样,确实是更复杂了,但是我们这个面向对象特别适合复杂的开发,对于简单的,不太推荐使用面向对象。面对复杂开发时,它特别容易扩展,同时,复用性特别强。上面的例子,多添加几个,就可以发现特别方便复用和扩展。

     

    4、命名空间

    // 使用场景:需要全局变量,但是你又不能直接声明全局变量。就用为你分配的命名空间来使用
    
    var U = {}; // 全网站唯一的一个全局变量
    
    // 开发张三
    U.zs = {};
    U.zs.a = 5;
    U.zs.fn = function () {
        console.log('前端开发');
    }
    
    // 开发李四
    U.ls = {};
    U.ls.a = 10;
    U.ls.fn = function () {
        console.log('约不');
    }
    
    console.log(U.zs.a); // 5
    console.log(U.ls.a); // 10

    5、call、apply

    • 函数.call(新的this指向, 参数1, 参数2, ...)

    • 函数.apply(新的this指向, [参数1, 参数2, ...])

    作用:调用函数,改变函数中的this指向

    区别:后续的参数传入不同,call的参数是一个一个传入的,而apply的参数是以一个数组的形式传入的

    function fn(a, b) {
        console.log(a, b);
        console.log(this);
    }
    
    var obj = {
        name: '张三',
        age: 3
    };
    
    // 普通调用
    fn(3, 5); // window
    
    // call调用
    fn.call(obj, 10, 20);
    
    // apply调用
    fn.apply(obj, [4, 5]);
    
    // -------------------------------
    // 求数组中的最大值
    var arr = [32, 4, 2, 433, 2, 3, 3, 5, 4, 44];
    
    console.log(Math.max(3, 5, 2, 67, 34));
    console.log(Math.max.apply(Math, arr));

    6、面向对象的继承

    1、原型链继承

    // 父类
    function Box() {
        this.name = 'Lee';
        this.arr = [1, 2, 3];
    }
    
    // 子类
    function Desk() {
        this.age = 10;
    }
    
    // 如何用一句话实现继承
    Desk.prototype = new Box(); // 原型链继承:将父的实例赋给子的原型
    
    var d1 = new Desk();
    console.log(d1);
    console.log(d1.age);
    console.log(d1.name);
    console.log(d1.arr);
    d1.arr.push(4);
    
    // 问题:如果继承了引用类型,会一改全改,一个实例对象改变了引用类型的数据,后面再创建出来的实例对象就已经是改过之后的数据。
    
    var d2 = new Desk();
    console.log(d2.arr);
    d2.arr.push(5);
    
    console.log(d1.arr);

    问题:如果继承了引用类型,会一改全改,一个实例对象改变了引用类型的数据,后面再创建出来的实例对象就已经是改过之后的数据。

     

    2、对象冒充继承

    // 父类
    function Box() {
        this.name = 'Lee';
        this.arr = [1, 2, 3];
    }
    Box.prototype.showName = function () {
        console.log(this.name);
    }
    
    // 子类
    // 对象冒充继承:在子的构造函数中,调用父的构造函数,并用call改this指向
    function Desk() {
        Box.call(this);
        this.age = 10;
    }
    
    var d1 = new Desk();
    console.log(d1);
    console.log(d1.name);
    console.log(d1.age);
    d1.arr.push(4);
    
    var d2 = new Desk();
    console.log(d2.arr);
    console.log(d2.showName); // 不足,不能继承父类原型上的东西

    不足,不能继承父类原型上的东西

     

    3、组合继承

    // 组合继承:对象冒充继承 + 原型链继承
    // 父类
    function Box() {
        this.name = 'Lee';
        this.arr = [1, 2, 3];
    }
    Box.prototype.showName = function () {
        console.log(this.name);
    }
    
    // 子类
    function Desk() {
        Box.call(this); // 对象冒充继承
        this.age = 10;
    }
    Desk.prototype = new Box(); // 原型链继承
    
    var d1 = new Desk();
    console.log(d1);
    
    console.log(d1.name);
    d1.arr.push(4);
    d1.showName();
    
    // -------------------
    // 问题:
    // 1、同样的属性,在原型链上出现了两次
    // 2、会两次调用父的构造函数

    问题: 1、同样的属性,在原型链上出现了两次 2、会两次调用父的构造函数

     

    4、寄生组合继承

    完美:继承的标准模式

    // 父类
    function Box() {
        this.name = 'Lee';
        this.arr = [1, 2, 3];
    }
    Box.prototype.showName = function () {
        console.log(this.name);
    }
    
    // 子类
    function Desk() {
        Box.call(this); // 对象冒充继承
        this.age = 10;
    }
    inherits(Desk, Box);
    Desk.prototype.showAge = function () {
        console.log(this.age);
    }
    // var F = function () { };
    // F.prototype = Box.prototype;
    // Desk.prototype = new F();
    // Desk.prototype.constructor = Desk;
    
    var d1 = new Desk();
    console.log(d1);
    
    // 寄生组合继承:(继承的标准模式)
    // 1、利用对象冒充继承父的属性
    // 2、原型链继承要修改一下,利用空函数转一下(4句话,一般封装成一个方法)
    function inherits(Child, Parent) {
        var F = function () { };
        F.prototype = Parent.prototype;
        Child.prototype = new F();
        Child.prototype.constructor = Child;
    }

     

     



  • 相关阅读:
    scrapy之download middleware
    远程采集
    未能加载文件或程序集“Oracle.DataAccess, Version=4.112.2.0, Culture=neutral, PublicKeyTok”
    【转】如何解决C盘根目录无法创建或写入文件
    C#报算术运算导致溢出的错误
    【转】C# String 前面不足位数补零的方法
    【转】C# 使用正则表达式去掉字符串中的数字,或者去掉字符串中的非数字
    【转】Asp.Net页面生命周期
    【转】processOnServer
    【转】oracle的分析函数over
  • 原文地址:https://www.cnblogs.com/cyf666cool/p/13702826.html
Copyright © 2011-2022 走看看