面向对象之构造函数及原型模式

鉴于总是资料丢失,借这个平台对知识进行梳理。

认识对象

在学习JS的时候对象是个比较抽象的东西,其实对象是单个事物的抽象。比如一间房,一辆车,一台电脑,一支笔都可以是一个对象。
对象是一个容器,封装了属性和方法。比如:一辆车。它的颜色,大小,重量等是它的属性,而启动,加速,减速,刹车等是它的方法。
这些特征,或者说是属性定义了一个物体由什么构成的。需要注意的是:那些相似的物体可以拥有相同的属性,但是这些属性可能会有不同的值。举个例子:所有的汽车都有轮子,但并不是所有汽车的轮子个数都是一样的。

面向对象编程和面向对象过程

请看如下代码:
需求:给这三个p设置边框.

<style>
    p {
      width: 100px;
      height: 100px;
      margin-top: 20px;
    }
  </style>
</head>
<body>
  <p></p>
  <p></p>
  <p></p>
</body>
</html

面向过程 注重过程===造轮子

  1.以前的做法
   let ps = document.getElementsByTagName('p');
   // //遍历出每一个p
   for (let i = 0; i < ps.length; i++) {
      ps[i].style.border = '1px solid green';
    }
   //2.上面的代码可以封装成函数
    function getEles(tagName) {
      return document.getElementsByTagName(tagName);
    }
   function setStyle(eles, value) {
     for (let i = 0; i < eles.length; i++) {
       eles[i].style.border = value;
     }
    }
   // //调用我们自己封装的函数来完成需求
    let ps = getEles('p');
    setStyle(ps, '1px solid red');

面向对象编程 注重结果===用现成的

   //3.把上面这个代码封装到一个对象中  面向对象思想. 
   let obj = {
     getEles: function (tagName) {
       return document.getElementsByTagName(tagName);
     },
     setStyle: function (eles, value) {
       for (var i = 0; i < eles.length; i++) {
         eles[i].style.border = value;
       }
     }
   }
   //调用我们自己封装的对象,里面专业的方法来完成我们的需求. 
   let ps = obj.getEles('p');
   obj.setStyle(ps,'1px solid blue');
 </script>

从以上代码中可以看出面向对象其实是面向过程的一种封装.

创建对象

首先先复习下对象的创建方式

1.通过对象字面量来创建。
var Person = {
  name: 'alanshiyi',
  age: 18,
  gender : 'male',
  sayHi: function () {
    console.log("hi,my name is "+this.name);
  }
}; 
2. 通过 new Object() 创建对象。
var Person= new Object();
  student.name = 'alanshiyi',
  student.age = 18,
  student.gender = 'male',
  student.sayHi = function () {
    console.log("hi,my name is "+this.name);
  }

上面两种都是简单的创建对象的方式,但是如果有n多个实例对象呢?显然如果这样做的法太过繁杂,是不可取的。所以做了简单的改进,就引入了:工厂函数。

3. 通过工厂函数来创建对象。
function createPerson(name, age, gender) {
  var person = new Object();
  person.name = name;
  person.age = age;
  person.gender = gender;
  person.sayHi = function(){
    console.log("hi,my name is "+this.name);
  }
  return student;
}
var s1 =  createPerson('lisi', 19, 'male');
var s2 =createPerson( 'alanshiyi', 18, 'male');

这样封装代码确实解决了代码冗余的问题,但是每次调用函数 createPerson() 都会创建新函数 sayHi(),也就是说每个对象都有自己的 sayHi() 版本,而事实上,每个对象都共享一个函数。为了解决这个问题,就引入面向对象编程里的一个重要概念:构造函数。

4. 通过构造函数来创建对象。
function Person(name,age,gender){
  this.name = name;
  this.age = age;
  this.gender = gender;
  this.sayHi = function(){
       console.log("hi,my name is "+this.name);
  }
}
var s1 = new Person( 'alanshiyi', 18, 'male');
对比一下构造函数与工厂函数的区别:
  • 首先在构造函数内没有创建对象,而是使用 this 关键字,将属性和方法赋给了 this 对象。
  • 构造函数内没有 return 语句,this 属性默认下是构造函数的返回值。
  • 函数名使用的是大写的 Student。
  • 用 new 运算符和类名 Student 创建对象。
构造函数虽然科学,但仍然存在一些问题
  function Student(name,age){
            this.name = name;
            this.age = age;
            this.sayHi = function(){
                console.log('我的名字是'+this.name);
            }
        }
        //实例化学生对象
        let s1 = new Student('达达',18);
        s1.sayHi();
        let s2 = new Student('云旺',19);
        s2.sayHi();
        //判断一下下面的这句话的结果是什么?
        console.log(s1.sayHi === s2.sayHi);//false

image.png

由于每个对象都是由 new Student 创建出来的,因此每创建一个对象,函数 sayHi 都会被重新创建一次,这个时候,每个对象都拥有一个独立的,但是功能完全相同的方法,这样势必会造成内存浪费。有的人可能会想,既然是一样的那我们就单独把它提出来,写一个函数,每次调用不就可以了吗?比如:

        function test1(){
            console.log('我的名字是'+this.name);
        }
        function Student(name,age){
            this.name = name;
            this.age = age;
            this.sayHi = test1;
        }
        //实例化学生对象
        let s1 = new Student('王晓',20);
        s1.sayHi();
        let s2 = new Student('郭荣',21);
        s2.sayHi();
        //判断一下下面的这句话的结果是什么?
        console.log(s1.sayHi === s2.sayHi);//true
image.png

解决空间浪费的问题: 把函数体提炼到构造函数外面
缺点:
但是这样做会导致全局变量增多,可能会引起命名冲突,代码结果混乱,有全局变量污染的危险,维护困难。
既然是全局变量的问题那可以把提炼出来的函数放在对象中.

  var obj = {
            test1: function () {
                console.log('我的名字是' + this.name);
            }
        }
        function Student(name, age) {
            this.name = name;
            this.age = age;
            this.sayHi = obj.test1;
        }
        //实例化学生对象
        let s1 = new Student('万山2', 30);
        s1.sayHi();
        let s2 = new Student('富婆娟2', 32);
        s2.sayHi();
        //判断一下下面的这句话的结果是什么?
        console.log(s1.sayHi === s2.sayHi); //true

以上方法似乎已经完美的解决了各种问题
但好的地方是: 每写一个构造函数,都要写一个和这个构造函数配套的对象. 所以就引出了原型的概念,因为在javascript不管哪个构造函数被创建,系统都会帮我们自动的创建一个与之对应的对象,这个对象就是原型. 所以每写一个构造函数,用原型对象匹配即可,无需再创建与构造函数配套的对象。

原型:prototype

在 JavaScript 中,每一个函数都有一个 prototype 属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。

我们来看看前面例子原型的写法:

 Student.prototype.test1= function () {
                console.log('我的名字是' + this.name);
            }
        
        function Student(name, age) {
            this.name = name;
            this.age = age;
            this.sayHi = test1;
        }
        //实例化学生对象
        let s1 = new Student('万山2', 30);
        s1.sayHi();
        let s2 = new Student('富婆娟2', 32);
        s2.sayHi();
        //判断一下下面的这句话的结果是什么?
        console.log(s1.sayHi === s2.sayHi); //true

原型对象访问的方式:构造函数名.prototype
原型对象添加/调用(属性/方法) 的方式:

   Student.prototype.sb = '随便';
        Student.prototype.dsb = function(){
            console.log('你是一个大随便...');
        }
        //调用原型对象中的属性和方法
        console.log(Student.prototype.sb);
       Student.prototype.dsb();
  • 使用原型需要注意的地方
  • 对象访问成员的访问规则:
    如果这个成员对象自己有,那就访问自己的; 如果对象自己没有,那就访问原型的,往原型链一层层往上找...(原型链在之后的文章再细谈)
  • 访问原型(给原型添加内容/修改原型中的内容/删除原型中的内容),一定要使用 构造函数名.prototype这种格式 ,原型可以被覆盖.

之前提到过:每一个函数都有一个 prototype 属性,指向另一个对象。

<script type="text/javascript">
    function F() {}
    console.log(F.prototype);//Object
</script>

上述代码在浏览器中打印结果为 Object,验证了我们所说的 prototype 属性,指向另一个对象。

构造函数的 prototype 对象默认都有一个 constructor 属性,指向 prototype 对象所在函数。在控制台中运行下面的代码:

function F() {}
console.log(F.prototype.constructor === F);//结果为ture

更简单的原型语法

在前面的例子中,我们是使用 xxx.prototype. 然后加上属性名或者方法名来写原型,但是每添加一个属性或者方法就写一次显得有点麻烦,因此我们可以用一个包含所有属性和方法的对象字面量来重写整个原型对象:

function Student(name, age, gender) {
    this.name = name;
    this.age = age;
    this.gender = gender;
}
Student.prototype = {
    hobby:"study",
    sayHi:function(){
    console.log("hi");
    }
}
var s1 = new Student("wangwu",18,"male");
console.log(Student.prototype.constructor === Student);//结果为 false

但是这样写也有一个问题,那就是原型对象丢失了 constructor 成员。所以为了保持 constructor 成员的指向正确,建议的写法是:

function Student(name, age, gender) {
    this.name = name;
    this.age = age;
    this.gender = gender;
}
Student.prototype = {
    constructor: Student, //手动将 constructor 指向正确的构造函数
    hobby:"study",
    sayHi:function(){
    console.log("hi");
    }
}
var s1 = new Student("wangwu",18,"male");
console.log(Student.prototype.constructor === Student);//结果为 true

通过构造函数得到的实例对象内部会包含一个指向构造函数的 prototype 对象的指针protoproto属性最早是火狐浏览器引入的,用以通过实例对象来访问原型,这个属性在早期是非标准的属性。在控制台中运行下面的代码:

function F() {}
var a = new F();
console.log(a.__proto__ === F.prototype); //结果为true

实例对象可以直接访问原型对象成员。所有实例都直接或间接继承了原型对象的成员。

image.png

总结:每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针 constructor,而实例都包含一个指向原型对象的内部指针proto

以上只是均来源于上课笔记与MDN及牛客网实验楼<<JS高级程序设计>>学习资料总结

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

推荐阅读更多精彩内容