JavaScript面向对象精要(二卡塔尔国

JavaScript
面向对象精要(一)

面向对象编程是用抽象方式创建基于现实世界模型的一种编程模式,主要包括模块化、多态、和封装几种技术。
对 JavaScript
而言,其核心是支持面向对象的,同时它也提供了强大灵活的基于原型的面向对象编程能力。
本文将会深入的探讨有关使用 JavaScript
进行面向对象编程的一些核心基础知识,包括对象的创建,继承机制,
最后还会简要的介绍如何借助 ES6
提供的新的类机制重写传统的JavaScript面向对象代码。

JavaScript构造函数详解,javascript构造函数

构造函数就是初始化一个实例对象,对象的prototype属性是继承一个实例对象。

  构造函数注意事项:

1.默认函数首字母大写

2.构造函数并没有显示返回任何东西。new
操作符会自动创建给定的类型并返回他们,当调用构造函数时,new会自动创建this对象,且类型就是构造函数类型。

3.也可以在构造函数中显示调用return.如果返回的值是一个对象,它会代替新创建的对象实例返回。如果返回的值是一个原始类型,它会被忽略,新创建的实例会被返回。    

 function Person( name){
        this.name =name;
      }
       var p1=new Person('John');

等同于:

   function person(name ){
        Object obj =new Object();
        obj.name =name;
         return obj;
      }
       var p1= person("John");

4.因为构造函数也是函数,所以可以直接被调用,但是它的返回值为undefine,此时构造函数里面的this对象等于全局this对象。this.name其实就是创建一个全局的变量name。在严格模式下,当你补通过new
调用Person构造函数会出现错误。

 ylg娱乐官网 1

5.也可以在构造函数中用Object.defineProperty()方法来帮助我们初始化:

  function Person( name){
        Object.defineProperty(this, "name"{
          get :function(){
             return name;
          },
           set:function (newName){
            name =newName;
          },
          enumerable :true, //可枚举,默认为false
           configurable:true //可配置
         });
      }  
       var p1=new Person('John');

6.在构造函数中使用原型对象      

 //比直接在构造函数中写的效率要高的多
       Person.prototype.sayName= function(){
         console.log(this.name);
      };

但是如果方法比较多的话,大多人会采用一种更简洁的方法:直接使用一个对象字面形式替换原型对象,如下:      

 Person.prototype ={
        sayName :function(){
           console.log(this.name);
        },
        toString :function(){
           return "[Person "+ this.name+"]" ;
        }
      };

这种方式非常流行,因为你不用多次键入Person.prototype,但有一个副作用你一定要注意:

ylg娱乐官网 2

使用字面量形式改写了原型对象改变了构造函数的属性,因此他指向Object而不是Person。这是因为原型对象具有一个constructor属性,这是其他对象实例所没有的。当一个函数被创建时,它的prototype属性也被创建,且该原型对象的constructor属性指向该函数。当使用对象字面量形式改写原型对象时,其constructor属性将被置为泛用对象Object.为了避免这一点,需要在改写原型对象的时候手动重置constructor,如下:

 Person.prototype ={
        constructor :Person,

        sayName :function(){
           console.log(this.name);
        },        
        toString :function(){
           return "[Person "+ this.name+"]" ;
        }
      };

再次测试:

p1.constructor===Person

true 

p1.constructor===Object

false

p1 instanceof Person

true

构造函数和原型对象

构造函数也是函数,用new创建对象时调用的函数,与普通函数的一个区别是,其首字母应该大写。但如果将构造函数当作普通函数调用(缺少new关键字),则应该注意this指向的问题。

var name = "Pomy";
function Per(){
    console.log("Hello "+this.name);
}
var per1 = new Per();  //"Hello undefined"
var per2 = Per();   //"Hello Pomy"

使用new时,会自动创建this对象,其类型为构造函数类型,指向对象实例;缺少new关键字,this指向全局对象。

可以用instanceof来检测对象类型,同时每个对象在创建时都自动拥有一个constructor属性,指向其构造函数(字面量形式或Object构造函数创建的对象,指向Object,自定义构造函数创建的对象则指向它的构造函数)。

console.log(per1 instanceof Per);  //true
console.log(per1.constructor === Per); //true

每个对象实例都有一个内部属性:[[Prototype]],其指向该对象的原型对象。构造函数本身也具有prototype
属性指向原型对象。所有创建的对象都共享该原型对象的属性和方法。

function Person(){}
Person.prototype.name="dwqs";
Person.prototype.age=20;
Person.prototype.sayName=function()
{
    alert(this.name);
};
var per1 = new Person();
per1.sayName();  //dwqs
var per2 = new Person();
per2.sayName();  //dwqs
alert(per1.sayName == per2.sayName);  //true

ylg娱乐官网 3

所以,实例中的指针仅指向原型,而不指向构造函数。 ES5提供了hasOwnProperty()和isPropertyOf()方法来反应原型对象和实例之间的关系

alert(Person.prototype.isPrototypeOf(per2));  //true
per1.blog = "www.ido321.com";
alert(per1.hasOwnProperty("blog"));  //true
alert(Person.prototype.hasOwnProperty("blog"));  //false
alert(per1.hasOwnProperty("name"));  //false
alert(Person.prototype.hasOwnProperty("name"));  //true

因为原型对象的constructor属性是指向构造函数本身,所以在重写原型时,需要注意constructor属性的指向问题。

function Hello(name){
    this.name = name;
}
//重写原型
Hello.prototype = {
    sayHi:function(){
        console.log(this.name);
    }
};
var hi = new Hello("Pomy");
console.log(hi instanceof Hello);  //true
console.log(hi.constructor === Hello); //false
console.log(hi.constructor === Object); //true

使用对象字面量形式改写原型对象改变了构造函数的属性,因此constructor指向Object,而不是Hello。如果constructor指向很重要,则需要在改写原型对象时手动重置其constructor属性

Hello.prototype = {
    constructor:Hello,
    sayHi:function(){
        console.log(this.name);
    }
};
console.log(hi.constructor === Hello); //true
console.log(hi.constructor === Object); //false

利用原型对象的特性,我们可以很方便的在JavaScript的内建原型对象上添加自定义方法:

Array.prototype.sum=function(){
    return this.reduce(function(prev,cur){
        return prev+cur;
    });
};
var num = [1,2,3,4,5,6];
var res = num.sum();
console.log(res);  //21
String.prototype.capit = function(){
    return this.charAt(0).toUpperCase()+this.substring(1);
};
var msg = "hello world";
console.log(msg.capit()); //"Hello World"

面向对象的几个概念
在进入正题前,先了解传统的面向对象编程(例如Java)中常会涉及到的概念,大致可以包括:

您可能感兴趣的文章:

  • JavaScript 构造函数 面相对象学习必备知识
  • JavaScript中使用构造函数实现继承的代码
  • Javascript面向对象编程(二) 构造函数的继承
  • Javascript面向对象编程(三) 非构造函数的继承
  • js面向对象设计用{}好还是function(){}好(构造函数)
  • 有关于JS构造函数的重载和工厂方法
  • JS特殊函数(Function()构造函数、函数直接量)区别介绍
  • JS中的构造函数详细解析
  • JS面向对象基础讲解(工厂模式、构造函数模式、原型模式、混合模式、动态原型模式)
  • JavaScript继承基础讲解(原型链、借用构造函数、混合模式、原型式继承、寄生式继承、寄生组合式继承)
  • 深入理解javascript构造函数和原型对象
  • js封装可使用的构造函数继承用法分析

构造函数就是初始化一个实例对象,对象的prototype属性是继承一个实例对象。
构造函数注意事项:…

ylg娱乐官网,继承

利用[[Prototype]]特性,可以实现原型继承;对于字面量形式的对象,会隐式指定Object.prototype为其[[Prototype]],也可以通过Object.create()显示指定,其接受两个参数:第一个是[[Prototype]]指向的对象(原型对象),第二个是可选的属性描述符对象。

var book = {
    title:"这是书名";
};
//和下面的方式一样
var book = Object.create(Object.prototype,{
    title:{
        configurable:true,
        enumerable:true,
        value:"这是书名",
        wratable:true
    }
});

字面量对象会默认继承自Object,更有趣的用法是,在自定义对象之间实现继承。

var book1 = {
    title:"JS高级程序设计",
    getTitle:function(){
        console.log(this.title);
    }
};
var book2 = Object.create(book1,{
    title:{
        configurable:true,
        enumerable:true,
        value:"JS权威指南",
        wratable:true
    }
});
book1.getTitle();  //"JS高级程序设计"
book2.getTitle();  //"JS权威指南"
console.log(book1.hasOwnProperty("getTitle"));  //true
console.log(book1.isPrototypeOf("book2"));  //false
console.log(book2.hasOwnProperty("getTitle"));  //false

当访问book2的getTitle属性时,JavaScript引擎会执行一个搜索过程:现在book2的自有属性中寻找,找到则使用,若没有找到,则搜索[[Prototype]],若没有找到,则继续搜索原型对象的[[Prototype]],直到继承链末端。末端通常是Object.prototype,其[[Prototype]]被设置为null。

实现继承的另外一种方式是利用构造函数。每个函数都具有可写的prototype属性,默认被自懂设置为继承自Object.prototype,可以通过改写它来改变原型链。

function Rect(length,width){
    this.length = length;
    this.width = width;
}
Rect.prototype.getArea = function(){
    return this.width * this.length;
};
Rect.prototype.toString = function(){
    return "[Rect"+this.length+"*"+this.width+"]";
};
function Square(size){
    this.length = size;
    this.width = size;
}
//修改prototype属性
Square.prototype = new Rect();
Square.prototype.constructor = Square;
Square.prototype.toString = function(){
    return "[Square"+this.length+"*"+this.width+"]";
};
var rect = new Rect(5,10);
var square = new Square(6);
console.log(rect.getArea());  //50
console.log(square.getArea());  //36

如果要访问父类的toString(),可以这样做:

Square.prototype.toString = function(){
    var text = Rect.prototype.toString.call(this);
    return text.replace("Rect","Square");
}

类:定义对象的特征。它是对象的属性和方法的模板定义。
对象(或称实例):类的一个实例。
属性:对象的特征,比如颜色、尺寸等。
方法:对象的行为,比如行走、说话等。
构造函数:对象初始化的瞬间被调用的方法。
继承:子类可以继承父类的特征。例如,猫继承了动物的一般特性。
封装:一种把数据和相关的方法绑定在一起使用的方法。
抽象:结合复杂的继承、方法、属性的对象能够模拟现实的模型。
多态:不同的类可以定义相同的方法或属性。
在 JavaScript
的面向对象编程中大体也包括这些。不过在称呼上可能稍有不同,例如,JavaScript
中没有原生的“类”的概念,
而只有对象的概念。因此,随着你认识的深入,我们会混用对象、实例、构造函数等概念。

对象(类)的创建
在JavaScript中,我们通常可以使用构造函数来创建特定类型的对象。诸如
Object 和 Array
这样的原生构造函数,在运行时会自动出现在执行环境中。此外,我们也可以创建自定义的构造函数。例如:

function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
}
var person1 = new Person('Weiwei', 27, 'Student');
var person2 = new Person('Lily', 25, 'Doctor');
function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
}
var person1 = new Person('Weiwei', 27, 'Student');
var person2 = new Person('Lily', 25, 'Doctor');

按照惯例,构造函数始终都应该以一个大写字母开头(和Java中定义的类一样),普通函数则小写字母开头。
要创建 Person 的新实例,必须使用 new 操作符。
以这种方式调用构造函数实际上会经历以下4个步骤:

创建一个新对象(实例)
将构造函数的作用域赋给新对象(也就是重设了this的指向,this就指向了这个新对象)
执行构造函数中的代码(为这个新对象添加属性)
返回新对象
在上面的例子中,我们创建了 Person 的两个实例 person1 和 person2 。
这两个对象默认都有一个 constructor 属性,该属性指向它们的构造函数
Person,也就是说:

console.log(person1.constructor == Person);  //true
console.log(person2.constructor == Person);  //true
1
2
console.log(person1.constructor == Person);  //true
console.log(person2.constructor == Person);  //true

自定义对象的类型检测
我们可以使用instanceof操作符进行类型检测。我们创建的所有对象既是Object的实例,同时也是Person的实例。
因为所有的对象都继承自Object。

console.log(person1 instanceof Object);  //true
console.log(person1 instanceof Person);  //true
console.log(person2 instanceof Object);  //true
console.log(person2 instanceof Person);  //true
console.log(person1 instanceof Object);  //true
console.log(person1 instanceof Person);  //true
console.log(person2 instanceof Object);  //true
console.log(person2 instanceof Person);  //true

构造函数的问题
我们不建议在构造函数中直接定义方法,如果这样做的话,每个方法都要在每个实例上重新创建一遍,这将非常损耗性能。
——不要忘了,ECMAScript中的函数是对象,每定义一个函数,也就实例化了一个对象。

幸运的是,在ECMAScript中,我们可以借助原型对象来解决这个问题。

借助原型模式定义对象的方法
我们创建的每个函数都有一个prototype属性,这个属性是一个指针,指向该函数的原型对象,
该对象包含了由特定类型的所有实例共享的属性和方法。也就是说,我们可以利用原型对象来让所有对象实例共享它所包含的属性和方法。

function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
}
// 通过原型模式来添加所有实例共享的方法
// sayName() 方法将会被Person的所有实例共享,而避免了重复创建
Person.prototype.sayName = function () {
  console.log(this.name);
};
var person1 = new Person('Weiwei', 27, 'Student');
var person2 = new Person('Lily', 25, 'Doctor');
console.log(person1.sayName === person2.sayName); // true
person1.sayName(); // Weiwei
person2.sayName(); // Lily
function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
}
// 通过原型模式来添加所有实例共享的方法
// sayName() 方法将会被Person的所有实例共享,而避免了重复创建
Person.prototype.sayName = function () {
  console.log(this.name);
};
var person1 = new Person('Weiwei', 27, 'Student');
var person2 = new Person('Lily', 25, 'Doctor');
console.log(person1.sayName === person2.sayName); // true
person1.sayName(); // Weiwei
person2.sayName(); // Lily

正如上面的代码所示,通过原型模式定义的方法sayName()为所有的实例所共享。也就是,
person1和person2访问的是同一个sayName()函数。同样的,公共属性也可以使用原型模式进行定义。例如:

function Chinese (name) {
    this.name = name;
}
Chinese.prototype.country = 'China'; // 公共属性,所有实例共享
function Chinese (name) {
    this.name = name;
}
Chinese.prototype.country = 'China'; // 公共属性,所有实例共享

当我们new
Person()时,返回的Person实例会结合构造函数中定义的属性、行为和原型中定义的属性、行为,
生成最终属于Person实例的属性和行为。

构造函数中定义的属性和行为的优先级要比原型中定义的属性和行为的优先级高,如果构造函数和原型中定义了同名的属性或行为,
构造函数中的属性或行为会覆盖原型中的同名的属性或行为。

原型对象
现在我们来深入的理解一下什么是原型对象。

只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。
在默认情况下,所有原型对象都会自动获得一个constructor属性,这个属性包含一个指向prototype属性所在函数的指针。
也就是说:Person.prototype.constructor指向Person构造函数。

创建了自定义的构造函数之后,其原型对象默认只会取得constructor属性;至于其他方法,则都是从Object继承而来的。
当调用构造函数创建一个新实例后,该实例内部将包含一个指针(内部属性),指向构造函数的原型对象。ES5中称这个指针为[[Prototype]],
在Firefox、Safari和Chrome在每个对象上都支持一个属性proto(目前已被废弃);而在其他实现中,这个属性对脚本则是完全不可见的。
要注意,这个链接存在于实例与构造函数的原型对象之间,而不是实例与构造函数之间。

这三者关系的示意图如下:

ylg娱乐官网 4

5bcc66ff9ce008c0c497cd7a39b34602.jpg

prototype graph

上图展示了Person构造函数、Person的原型对象以及Person现有的两个实例之间的关系。

Person.prototype指向了原型对象
Person.prototype.constructor又指回了Person构造函数
Person的每个实例person1和person2都包含一个内部属性(通常为proto),person1.proto和person2.proto指向了原型对象
查找对象属性
从上图我们发现,虽然Person的两个实例都不包含属性和方法,但我们却可以调用person1.sayName()。
这是通过查找对象属性的过程来实现的。

搜索首先从对象实例本身开始(实例person1有sayName属性吗?——没有)
如果没找到,则继续搜索指针指向的原型对象(person1.proto有sayName属性吗?——有)
这也是多个对象实例共享原型所保存的属性和方法的基本原理。

发表评论

电子邮件地址不会被公开。 必填项已用*标注