JS面向对象、原型、继承

1.JS对象创建的三种方式

1.第一种形式 工厂模型

            function createPerson(name , sex , age){
                var obj = new Object();
                obj.name = name ; 
                obj.sex = sex ;
                obj.age = age ;
                obj.sayName = function(){
                    alert(this.name);
                }
                return obj;
            }
            
            var p1 = createPerson('z3' , '男' , 20);
            var p2 = createPerson('z4' , '女' , 25);
            
            //alert(p1.sex);
            p1.sayName();
    ```
            
2. 构造函数式, 例如new Array new Date
        // 函数的第一个字母大写 (类的模版)
        function Person(name , age , sex){
            this.name = name ;
            this.age  = age ; 
            this.sex  = sex ;
            this.sayName = function(){
                alert(this.name);
            }
        }
        //构造一个对象 new关键字 传递参数 执行模版代码 返回对象
        var p1 = new Person('小1' , 20 , '男');
        var p2 = new Person('小2' , 21 , '女');
        //alert(p1.name);
        //p1.sayName();
        //alert(p1 == p2); //类的概念:根据模版创建出不同的实例对象 
        
        //alert(p1.constructor == Person);
        //alert(p2.constructor == Person);
    
        alert(p1 instanceof Person);    
        alert(p1 instanceof Object);        
3.创建对象的方式:
        // 1当作构造函数去使用 :
        var p1 = new Person('小1' , 20 , '男');

        // 2作为普通的函数去调用
        Person('小2' , 25 , '男');        //在全局环境里定义属性并复制 直接定义在window上
        //alert(name);  // 小2

        // 3在另一个对象的作用域中调用
        var o = new Object();  
        // call  applly
        Person.call(o,'小4' , 12 , '女');
        alert(o.name);   // 小4
#####2.原型 prototype
原型:原型对象里的所有属性和方法被所有构造函数实例化出来的对象所共享
//使用构造函数方式创建对象
                function Person(name , age){
                    this.name = name ;
                    this.age  = age ;
                    this.sayName = function(){alert('我是姓名!')};

                }

                var p1 = new Person('z3',20);
                var p2 = new Person('z4',21);
                p1.sayName();  // 我是姓名!
                p2.sayName();  // 我是姓名!
                alert(p1.sayName == p2.sayName);     // false  每次创建对象都新建一个函数(实例化一个方法)
                alert(p1.prototype == p2.prototype); // true
可以这样避免每次创建对象都新建一个函数(实例化一个方法)
                function Person(name , age){
                    this.name = name ;
                    this.age  = age ;

                    this.sayName = sayName ;
                }
                // 定义了一次函数
                function sayName(){
                    alert(this.name);
                }
                var p1 = new Person('z3',20);
                var p2 = new Person('z4',21);
                p1.sayName();  // z3
                p2.sayName();  // z4
                alert(p1.sayName == p2.sayName);     // true
                alert(p1.name == p2.name);       // false
> prototype 创建每一个函数都有一个prototype属性,这个属性其实是一个指针,而这个指针总指向一个对象
这个对象的用途就是将特定的属性和方法包含在内,起到一个所有实例所共享的作用



 利用原型对象避免 使用构造行数创建对象时没次都新建一个函数
                function Person(){
                }
                var obj = Person.prototype;
                alert(typeof obj);  //object
                // 公共属性 name  age  sayName
                obj.name = 'z3';
                obj.age  = 20 ;
                obj.sayName = function(){alert(this.name);};

                var p1 = new Person();
                var p2 = new Person();
                alert(p1.age);  // 20
                alert(p2.age);  // 20
                p1.sayName();  // z3
                p2.sayName();  // z3
                alert(p1.sayName == p2.sayName); // true  可见对象是使用一个函数创建而来
                p1.name = 'lisi';
                alert(p2.name);  // z3   不影响对象
关于JS原型,    每一个对象都有prototype属性,该属性指向原型对象。 每个对象共享原型对象中的属性

![原型图例](http://upload-images.jianshu.io/upload_images/1278799-d856eec34d339504.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

构造函数  原型对象 实例对象的关系
                构造函数.prototype =  原型对象
                原型对象.constructor = 构造函数
                实例对象.prototype = 原型对象   

 >isPrototypeOf(); 判断原型的方法
 >Object.getPrototypeOf()  根据实例对象获得原型对象

 每次当代码读取一个对象的属性的时候,首先会进行一次搜索,搜索实例对象里name的属性,看看有没有。如果没有,再去实例所对应的原型对象里去搜索name属性,如果有则返回, 没有返回undefined

>hasOwnProperty(属性名)  判断一个对象的属性是属于原型属性  还是属于实例属性

function Person(){
}
// 添加原型属性
Person.prototype.name = 'z3';
Person.prototype.age = 20;
Person.prototype.sayName = function () {
alert('我是原型对象的方法');
};
// 用Person实例化的所有对象都共享属性 name age sayName

    var p3 = new Person();
    alert(p3.hasOwnProperty('name'));  // false
>in 操作符  判断属性是否存在于实例对象或原型对象中    如果其中一个存在就返回true

    var p1 = new Person();
    alert('name' in p1); // true
    var p2 = new Person();
    p2.name = 'w3';
    alert('name' in p2); // true
所以 判断一个属性是否存在原型中,可以使用 hasOwnProperty和in结合判断
    //  object:当前对象   name:要判断的属性
    function hasPrototypeProperty(object, name) {
        return !object.hasOwnProperty(name) && name in object;
    }
> Object.keys()  拿到当前对象里的所有keys 返回一个数组(不可被枚举的constructor属性除外)

> Object.getOwnPropertyNames()  枚举对象所有的属性,不管该内部属性能否被枚举

    var p1 = new Person();
    p1.name = 'z3';
    p1.age = 20;
    var attributes = Object.keys(p1);
    alert(attributes); //name,age

    var attributes2 = Object.keys(Person.prototype);
    alert(attributes2); //name,age,sayName      原型对象的属性数组

    // constructor属性, 该属性是不能被枚举的,[eable = false]
    // Object.getOwnPropertyNames()  枚举对象所有的属性,不管该内部属性能否被枚举
    var attributes3 = Object.getOwnPropertyNames(Person.prototype);
    alert(attributes3);  //constructor,name,age,sayName

> 使用原型扩展对象中的属性和方法  

例如:模拟Array中的each循环方法
Array中的forEach方法,只适合遍历一维数组, 使用原型扩展一个each方法,完成遍历多维数组
   var arr = [1,2,3,[4,[5]]];
    arr.forEach(function (item, index, array) {
        alert(item);   // 1 2 3    4,5
    });
使用原型对象实现一个Array的each方法,遍历多维数组
    var arr = [1,2,3,[4,[5,[6]]]];
    // 在原型对象上添加方法
    Array.prototype.each = function (fn) {
        try{
            // 目的:遍历数组的每一项
            this.i || (this.i = 0); // 计数器,记录当前元素的位置   没有使用var i= 0; 是为了避免变量冲突
            // 严谨的判断:什么时候走each核心方法
            // 当数组的长度大于0时并且传递的参数必须为函数fn.constructor == Function
            if(this.length > 0 && fn.constructor == Function) {
                // 循环遍历数组的每一项
                 while(this.i < this.length){  // 底层代码少写forin循环 避免变量冲突
                    // 核心代码, 获取数组的每一项
                     var e = this[this.i];
                     // 如果当前元素获取到了  并且是一个数组
                     if(e && e.constructor == Array){
                         //直接做递归操作
                         e.each(fn);
                     } else {
                         // 如果不是数组(单个的元素)
                         //fn(e);

                         //var obj = true;
                         //fn.apply(obj, [e]);

                         //这的目的就是为了把数组的当前元素传递给fn函数 并让函数执行
                         //fn.apply(e, [e]);
                         fn.call(e,[e]);
                     }

                     this.i++;
                }
                this.i == null; // 释放内存 垃圾回收机制回收变量
            }

        } catch(ex){
            // do something
        }
        return this;  // this 指向方法的调用者
    }


    // 运行
    arr.each(function (item) {
        alert(item); // 1   2   3   4   5  6
    });
>简单原型

直接通过对象字面量来重写整个原型对象(这种方法会改变原型对象的构造器)
    function Person(){
            }
    Person.prototype = {  // 给原型对象重新设置了一个匿名方法,原型对象的构造器不再是Person(){};
        // 需要给原型对象添加构造器
        // constructor:Person, // 将构造器属性直接写在这里,这样不行,以为constructor是不可枚举的,在这里可以枚举得到
        name:'z3',
        age:20,
        job:'程序员',
        say: function () {
            alert('我是原型的函数!');
        }
    };

    // 使用Object.defineProperty()方法给原型对象重新设置构造器
    // 参数1: 重设构造器的对象  参数2:设置什么属性  参数3:option配置项
    Object.defineProperty(Person.prototype, 'constructor', {
        enumerable: false,
        value:Person
    });
原型具有动态性,在普通原型中可以先创建对象,再添加原型属性,例如:
    function Person(){
            }
    var p1 = new Person();
    // 原型对象的构造器默认为Person
    Person.prototype.say = function () {
        alert('我是方法!')
    };
    p1.say();  // 我是方法!
但是使用简单原型时,是不可以先创建对象后添加原型属性的
    function Person(){
    }
    var p1 = new Person();  // 原型对象是空的

    Person.prototype = {   // 给原型对象重新设置了一个匿名方法,原型对象的构造器不再是Person(){};
        say: function () {
            alert('我是原型的函数!');
        }
    };
    Object.defineProperty(Person.prototype, 'constructor', {
        enumerable:false,
        value: Person
    });

    p1.say();  //爆错: p1.say is not a function(…)  因为原型对象里面没有任何属性和方法


    // 注意:简单原型的使用顺序(实例对象必须在原型对象之后创建)
    var p2 = new Person();
    p2.say(); // 我是原型的函数!

>原型的弊端和创建对象的方式

原型里的属性和方法被所有对象所共享,修改一个对象的属性,两个一个对象的属性也变了
    function Person(){
    }
    Person.prototype = {
        name:'z3',
        age:20,
        job:'程序员',
        friends:['李四', '王五'],
        sayName: function () {
            alert('我的名字!')
        }
    };
    Object.defineProperty(Person.prototype, 'constructor', {
        enumerable: false,
        value:Person
    })

    var p1 = new Person();
    var p2 = new Person();
    p1.friends.push('赵六');

    alert(p2.friends);  // 李四,王五,赵六
    // 原型里的属性和方法被所有对象所共享, static的

创建对象的方式:
1.组合使用原型和构造函数式  (定义一个类,开发常用的方法)

function Person(name, age, friends, job) {
this.name = name;
this.age = age;
this.friends = friends;
this.job = job;
}
Person.prototype = {
sayName: function () {
alert(this.name);
}
};
Object.defineProperty(Person.prototype, 'constructor', {
enumerable: false,
value:Person
})

    var p1 = new Person('z3', 20, ['w5', 'z6'], '技术总监');
    var p2 = new Person('l4', 18, ['w5', 'z6'], 'boss');
    p1.friends.push('赵六');
    alert(p1.friends);  // w5, z6, 赵六
    alert(p2.friends); // w5, z6
2. 动态原型模式(让属性和方法 都封装到一起)

function Person(name, age, friends, job) {
this.name = name;
this.age = age;
this.friends = friends;
this.job = job;

        // 动态原型方法
        if(typeof this.sayName != 'function'){
            Person.prototype.sayName = function () {
                alert(this.name);
            }
        }
    }
3.稳妥构造函数式:durable object(稳妥对象)  在非常安全的环境中
特点:1.没有公共属性   2.不能使用this对象
    function Person(name, age, job){
        // 创建一个要返回的对象
        var obj = new Object();
        // 可以定义一些私有的变量和函数
        var sex = '男';
        var saySex = function () {};

        var name = name;
        // 添加对外方法
        obj.sayName = function () {
            alert(name);
        }
        return obj;
    }

    var p1 = new Person('z3',20,'程序员');
    p1.sayName();  // 只能通过方法访问名字

#####3.继承
 ######JS采用原型链的概念实现继承

 构造函数、原型对象和实例对象三者之间的关系有:
1. 构造函数.prototype = 原型对象
2. 原型对象.constructor = 构造函数(模版)
3. 原型对象.isPrototypeOf(实例对象),判断实例对象的原型 是不是当前对象
4. 构造函数与实例对象是类和实例的关系

根据它们的关系实现JS的继承

    // 父类构造函数
    function Sup(name){
        this.name = name;
    }
    // 父类原型对象
    Sup.prototype = {
        sayName: function () {
            alert(this.name);
        }
    }
    Object.defineProperty(Sup.prototype, 'constructor', {
        enumerable:false,
        value: Sup
    });

    //子类构造函数
    function Sub(age){
        this.age = age;
    }


    // 让子类的原型对象等父类的实例,结果会怎么样?  实现JS的继承
    // 1.此时的原型对象包含一个指向另一个原型的指针
    // sup的实例对象 和 sup的原型对象 有一个关系
    // 2.相应的另一个原型中也包含了一个指向另一个构造函数的指针
    // 子类的原型对象的构造器变成了父类的构造器

    alert(Sub.prototype.constructor);  // 结果 function Sub(age){this.age = age;}

    Sub.prototype = new Sup('z3');

    alert(Sub.prototype.constructor);  // 结果  function Sup(name){this.name = name;}
    var sub1 = new Sub(19);
    alert(sub1.name); // z3
    alert(sub1.age);  // 19

    sub1.sayName();  // z3
    sub1.name = 'lisi';
    sub1.sayName();  // lisi
 ######继承的方式
1.原型继承   即继承了父类的模板也继承了父类的原型对象

// 父类
function Person(name, age){
this.name = name;
this.age = age;
}
// 父类的原型对象属性
Person.prototype.id = 10;
//子类
function Boy(sex){
this.sex = sex;
}

    // 继承已经实现了
    Boy.prototype = new Person('z3');
    var b = new Boy();
    alert(b.name);  // z3
    alert(b.id);    // 10
2.类继承   只继承模板不继承原型对象(借用构造函数方式继承)

function Person(name, age){
this.name = name;
this.age = age;
}
// 父类的原型对象属性
Person.prototype.id = 10;
//子类
function Boy(name, age, sex){
// call apply
Person.call(this, name, age);
this.sex = sex;
}

    var b = new Boy('z3', 20, '男');
    alert(b.name); // z3
    alert(b.age);  // 20
    alert(b.sex);  // 男
    alert(b.id);  //  undefined  父类的原型对象并没有继承
3.原型继承+借用构造函数 = 混合继承

function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.id = 10;
Person.prototype.sayName = function(){alert(this.name);};
function Boy(name, age, sex){
// call apply
Person.call(this, name, age); // 1.借用构造函数继承,复制了父类的模板
this.sex = sex;
}
// 2.原型继承
Boy.prototype = new Person(); //继承父类的原型对象和复制了父类的模板
//存在的缺点就是复制了两次父类的模板

4.混合继承

function extend(sub ,sup){
// 目的: 实现只继承父类的原型对象
var F = new Function(); // 1 创建一个空函数 目的:空函数进行中转
F.prototype = sup.prototype; // 2 实现空函数的原型对象和超类的原型对象转换
sub.prototype = new F(); // 3 原型继承
sub.prototype.constructor = sub ; // 4还原子类的构造器
//保存一下父类的原型对象: 一方面方便解耦 另一方面方便获得父类的原型对象
sub.superClass = sup.prototype; //自定义一个子类的静态属性 接受父类的原型对象
//判断父类的原型对象的构造器 (加保险)
if(sup.prototype.constructor == Object.prototype.constructor){
sup.prototype.constructor = sup ; //手动欢迎父类原型对象的构造器
}
}

        // 混合继承:原型继承和借用构造函数继承
        function Person( name , age){
            this.name = name ; 
            this.age = age ; 
        }
        Person.prototype = {
            constructor: Person ,
            sayHello: function(){
                alert('hello world!');
            }
        };
        
        function Boy(name , age , sex){
            //call 绑定父类的模版函数 实现 借用构造函数继承 只复制了父类的模版
            Boy.superClass.constructor.call(this , name , age);
            this.sex = sex ;
        }
        
        //原型继承的方式: 即继承了父类的模版 又继承了父类的原型对象
        //Boy.prototype = new Person();
        // 只继承一遍父类的原型对象
        extend(Boy , Person);
        
        // 给子类加了一个 原型对象的方法
        Boy.prototype.sayHello = function(){
            alert('hi javascript!');
        }
        
        var b = new Boy('张三' , 20 , '男');
        alert(b.name); 
        alert(b.sex);
        //b.sayHello();
        Boy.superClass.sayHello.call(b);
        
        //alert(Boy.superClass.constructor);
        // 混合继承的缺点: 3件事 : 继承了父类的2次模版 , 继承了一次父类的原型对象
        // extend方法 2件事: 继承1次父类的模版 继承一次父类的原型对象






参考白贺翔教程
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容