对于面对对象和prototype的理解

来源:互联网 发布:世界历史书 知乎 编辑:程序博客网 时间:2024/06/04 19:20

面向过程和面向对象

面向过程和面向对象的区别

  • 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。就是使用函数将一个大的功能分成很多小功能,每个功能称之为一个过程,这就是面向过程编程。

  • 面向对象是把在解决问题中构成问题的事项分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

面向对象的优点

  • 单一职责
  • 代码清晰
  • 容易维护
  • 容易发现问题
  • 代码可读性好
  • 易于团队化作战 – 一个制造工具,一个使用工具
  • 解耦和:先互相分离再联合起来。

面向对象所谓的解耦是把原来引用很紧密的对象通过中间的一层进行分离,不直接引用,可能是只引用接口。这样的话,因为不直接引用,便与修改和维护。

举个栗子
例如:开发一个购物车,将产品相关代码放在一起,将购物车相关代码放在一起。
使用的时候只需要使用某个工具(封装好功能的函数)即可。

面对对象中的构造函数

怎么理解构造函数

构造函数:所谓”构造函数”,其实就是一个普通函数,但是内部使用了this变量。对构造函数使用new运算符,就能生成实例,并且this变量会指向实例对象。

构造函数每次实例化都是重新构造。

<script>    function Person(name,age) {        this.name = name;        this.age = age;        this.showName = function() {            alert("我的名字是" + name);        }        this.showAge = function() {            alert("我的年龄是"+ age);        }    }    var demo =  new Person("刘德华",18);    var demo1 = new Person("刘德华",18);    console.log(demo.showName);    console.log(demo1.showName);    console.log(demo.showName == demo1.showName);  // false    // 返回false是因为this的指向不一样,一个是demo,一个是demo1。    console.log(demo.name == demo1.name)  // true</script>

什么是this?

this:this指向的是被调用的函数的调用者(是谁调用了这个函数)和正在执行的事件的对象。

<script>    function Person(name,age) {        this.name = name;        this.age = age;        console.log(this);    }    Person();  // this指向window    var xiaoming = new Person(); // this指向 xiaoming这个对象</script>

创建一个函数,调用该函数,此时的this指向的是window,其实可以看成是window.Person()。
当我们实例化一个对象后,this就指向的是这个对象。

prototype和proto(这里的下划线显示不出来)

prototype和proto

  • prototype(显式原型):每一个函数在创建之后都会拥有一个名为prototype的属性,这个属性指向函数的原型对象,可以理解为prototype(显式原型)是针对于函数来说的。

  • proto(隐式原型):JavaScript中任意对象都有一个内置属性[[prototype]],在ES5之前没有标准的方法访问这个内置属性,但是大多数浏览器都支持通过proto来访问。ES5中有了对于这个内置属性标准的Get方法Object.getPrototypeOf(),可以理解为proto(隐式原型)是针对对象来说的。

  • 补充: 一个对象的原型也是一个对象,Object.prototype (对象的原型对象)是个例外,它的proto值为null(具体可以看原型链的相关内容)。

两者的关系和作用

  • 关系:隐式原型指向创建这个对象的函数(constructor)的prototype。

  • 显式原型的作用:用来实现基于原型的继承与属性的共享。

  • 隐式原型的作用:构成原型链,同样用于实现基于原型的继承。举个例子,当我们访问obj这个对象中的x属性时,如果在obj中找不到,那么就会沿着proto依次查找。

new关键字都干了些什么?

function Person() {};var xiaoming = new Person();

第一,我们创建了一个空对象xiaoming。

第二,我们将这个空对象的proto指针指向了Person函数的prototype对象。

第三,执行Person这个构造函数,如果Person有返回值(有return)直接将返回值返回给xiaoming实例对象,如果构造函数中没有返回值,就会直接把Person构造函数中的this对象返回给xiaoming实例对象,可以理解为克隆了一份Person构造函数中的this对象,给了xiaoming这个实例对象并保存在计算机内存中。

原型继承的优点(内存)

先理解一下内存的概念

一切数据通过变量来管理,定义变量的过程其实就是内存分配的过程,所以本质上一切数据都是存放在内存中的,一切变量,包括对象都是在内存中管理的。

实例对象也是一个变量,他也是放在内存中的,当我们实例化一个对象,其实就是将构造函数的属性拷贝一份,同时在内存中开辟一段新的区域保存这些值。

我们定义一个构造函数是不分配内存的,只有实例化的时候才分配内存。

实例对象是如何在内存存储的,指针是什么?

当你实例化一个对象,那么内存会开辟两个内存区域:
一个保存实例的名称变量,其实保存的只是地址,也可以把它理解为指针,它指向的是地址所对应的真正数据,类似于门牌号。
另一个保存对象的真正数据。

基于上面的理论,可以来谈谈用原型继承的优点了

构造函数不论被实例化多少次,它的原型对象都只会生成一次,实例化的时候只拷贝构造函数中的属性和方法,而不会拷贝构造函数原型对象的属性和方法。当我们new一个实例对象时,其中的一个作用就是让实例对象的原型指针指向构造函数的原型,所以实例对象的原型中保存的并不是从构造函数原型中继承的所有内容,而只是一个地址,一个指针。构造函数原型对象的内容并不会被拷贝一份给实例对象。这样一来,就节约了内存空间。

总而言之,构造函数实例化后,实例对象的原型都指向的是同一个原型,即是这个构造函数的原型。

通过下面这个例子理解一下:

<script>    function Person(name,age) {        this.name = name;        this.age = age;        this.showAge = function () {            alert("我的年龄是"+ this.age);        };    }    Person.prototype.showName = function() {        alert("我的名字是"+ this.name);    };    var demo = new Person("刘德华",15);    var demo1 = new Person("刘德华",15);    alert(demo.showName === demo1.showName);  // 返回true    alert(demo.showAge === demo1.showAge);    // 返回false</script>

实例化了两个对象demo和demo1,他们的原型都指向的构造函数Person的原型,但是this的指向却是不同的。

属性屏蔽理论

因为调用一个函数的方法和属性时,是先在函数本身里面寻找,找不到就会在函数的原型对象里寻找,如果原型对象中和构造函数中有相同的属性的话,就会显示构造函数中的属性。

我们想访问原型中的被屏蔽掉的属性有两种方法:

  1. delete
  2. 使用Product.prototype.属性或者方法名称
<script>    function Product(){        this.name='神仙';        this.description = ''        this.buy = function(){            alert('构造函数对象')        }    }        Product.prototype={            name:'魔鬼',            buy:function(){                alert('原型对象')            }        }        var product = new Product()        console.log(product.name)  // 神仙        /*清除构造函数中的name*/        delete product.name  // 清除后显示原型对象中的name属性        console.log(product.name) // 魔鬼        product.name='魔鬼2'  // 再次给name赋值        console.log(product.name) // 魔鬼2        /*没有清除之前,也可以用这种Product.prototype.buy()式来获取原型方法*/        product.buy(); // 构造函数对象        Product.prototype.buy();  // 原型对象        console.log(product.buy) // function (){alert('构造函数对象')}        /*清除*/        delete product.buy        console.log(product.buy) //function (){alert('原型对象')}        product.buy(); // 原型对象</script>
var fun = function(){}    fun.prototype = {        name:'1',        info : {            name : 'peter',            age : 25        }    }    var a = new fun();    var b = new fun();    a.info.name = 'jack';    b.info.name = 'tom';    console.log(a.info.name) // tom    console.log(b.info.name) // tom    都是指向的一个原型,原型的info.name最后一次被修改是tom,所有返回的都是tom。
原创粉丝点击