JavaScript之面向对象(原型、this、继承等一网打尽)

一、几个重点

  • 对象
  • 原型
  • 实例属性与原型属性
  • 继承

二、详细介绍

2.1 对象

对象的创建方法有以下:

  • 字面量
  • Object.Create()
2.2 原型

几乎所有对象都有一个prototype的默认属性。prototype属性的值,也是一个对象,它被称为“原型对象”,所以我们可以通过prototype属性,给“原型对象”添加其他一些属性。

  • 原型链

    查找属性时会用到,先看一个简单的例子,后面我们会详细介绍。

function Player() {

 }

Player.prototype.userBat = function () {
  return true;
}

Player.prototype.getPlayerName = function () {
  return "ygm"
}
//以一般函数的形式调用,并没有实例化对象,所以obj不是一个对象
//(实际打印结果为undefined,因为函数没有返回值),所以没有prototype
var obj = Player();
//TypeError: Cannot read property 'getPlayerName' of undefined
//console.log(obj.getPlayerName());


//以构造函数的形式调用,得到一个实例对象
var obj1 = new Player();
console.log(obj1.getPlayerName()); //ygm
console.log(obj1.userBat()); //true
//如果改成下面
function Player() {
  this.getPlayerName = function() {
    return "sub ygm"
  }
 }
Player.prototype.getPlayerName = function () {
  return "ygm"
}
var obj1 = new Player();
console.log(obj1.getPlayerName()); //sub ygm 这就是原型链的效果

2.3 实例属性和原型属性

  • 理解原型属性和实例属性

从上面的例子可以看出,当同一个属性同时出现在构造函数的属性列表和原型的属性列表中,构造函数的属性优先级更高,结果是构造函数的实例对象的对应属性指向的方法被调用。这是因为程序运行过程在查找原型链时,是从下往上查找的。【对比作用域链,它们的逻辑很类似,作用域链则是从内向外查找变量的,仅当补充,具体可以查看作用域链相关的文章】。

我们还可以这么理解:构造函数的属性,和prototype指向的“原型对象”的属性,都绑定到了构造函数的实例对象上。而其中相同的属性被加以区分,区分的原则是:这个属性来自构造函数还是来自prototype,对他们标上优先级,来自构造函数的优先级要高于来自prototype的同名属性。【但这种理解只是辅助理解,实际并非如此,请继续看下去】

//如果改成下面
function Player() {
  this.getPlayerName = function() {
    return "sub ygm"
  }
 }

var obj1 = new Player();
console.log(obj1.getPlayerName()); //sub ygm 这就是原型链的效果
console.log(obj1.userBat()); //true
  • 理解关键字【this】

理解【this指向谁】的问题,主要抓住以下3个方面:

  1. 当this用于全局上下文中,this会被绑定到全局上下文。

    function global() { 
      return this;
     }
    
     //在全局上下文中被调用
    //console.log(global());
    //Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
  2. 当this用于对象方法中,this被赋值或绑定到包含对象上

    【注意】:当对象存在嵌套时,包含对象是哪个直接的父对象(也就是包含对象是指直接包含的那个对象)

    var obj = {
      name:"obj",
      func:function(){
        return this;
      }
    }
    
    //this用于对象方法中,thisz被赋值或绑定包含对象上
    // obj是此时的包含对象
    console.log(obj.func());
    //{name: "obj", func: ƒ}
    
    //对象嵌套的情况
    var obj = {
      name:"obj",
      func:function(){
          return {
          name:"subObj",
          func:function(){
            return this;
          }
        }
      }
    }
    //此时被返回的对象才是包含对象
    console.log(obj.func());
    //{ name: 'subObj', func: [Function: func] }
  3. 当this用于构造函数中,this指向被构造出来的对象。

    var menber = "global member";
    //this用于构造函数中,this指向被构造的实例对象
    function constructor() {
      this.menber = "local member";
    }
    
    function constructor1() {
      console.log("global var is:", this.menber);//global var is: global member
      this.menber = this.menber + " changed";
    
      console.log("after chaneged, global var is:", this.menber);
      //after chaneged, global var is: global member changed
    }
    
    var obj_cst = new constructor();
    console.log(obj_cst.menber);//local member
    
    // 函数调用,它的this指向的是全局上下文
    constructor1();
    console.log(menber)//global member changed

    如果没有上下文,默认情况下this会绑定到全局上下文。

2.4 函数作用域实现访问控制

JavaScript是如何控制对象的可见性及其访问方式的呢?我们知道JavaScript并没有访问控制修饰器(public等),但是,之前我么学过的闭包与函数作用域等机制就起到了帮助,有了这些,我们可以完成对象的访问控制。

function Foo(name) { 
  //私有变量
  var local = "local variance";
  //private方法
  function countPlus() { 
    count++;
   }
   var count = 0;
  //public方法
  this.setLocal= function(name) {
    local = name;
    countPlus();
  }
  this.getLocal = function (name) { 
    return local;
   }
   //public 属性
   this.onwer = "you name";
 }

 //公共属性
 Foo.prototype.count = 0;
 //公共方法
 Foo.prototype.setCount = function () { 
   this.count = 0;
  }

建议使用控制台,打印看看,Foo.prototype, obj.__proto__分别是什么。

2.5 理解JavaScript的继承

  • 通过将对象的实例作为原型来实现

    function Person() {}
    Person.prototype.hasName = function () {
      console.log("yes");
      }
    
      function Child() {  }
    
      Child.prototype = new Person();
    
      var child = new Child();
    
    console.log(child instanceof Child);//true
    console.log(child instanceof Person);//true
    console.log(child instanceof Object);//true
    child.hasName()//yes
  • 更复杂的继承
    这里写图片描述
function Employee() {
  this.name = "";
  this.dept = "None";
  this.salary = 0.00;
  }

function Manager() {
  Employee.call(this);
  this.reports = [];
  }

Manager.prototype = Object.create(Employee.prototype);

function IndividualContributor() {
  Employee.call(this);
  this.active_projects = [];
  }

IndividualContributor.prototype = Object.create(Employee.prototype);

function TeamLead() {
  Manager.call(this);
  this.dept = "Software";
  this.salary = 10000;
  }
TeamLead.prototype = Object.create(Manager.prototype);

function Engineer() {
  TeamLead.call(this);
  this.dept = "Javascript";
  this.desktop_id = "007";
  this.salary = 8000;
  }

Engineer.prototype = Object.create(TeamLead.prototype);

var emp = new Employee();
console.log(emp);
//Employee { name: '', dept: 'None', salary: 0 }
var manager = new Manager();
manager.name = "ygm";
manager.reports = ["cat", "fat", "hat"];
console.log(manager);
// Employee {
//   name: 'ygm',
//   dept: 'None',
//   salary: 0,
//   reports: [ 'cat', 'fat', 'hat' ] }
var teamleader = new TeamLead();
teamleader.name = "Json";
console.log(teamleader);
//Employee { name: 'Json', dept: 'Software', salary: 10000, reports: [] }

var engineer = new Engineer();
engineer.name = "凌凌漆";
console.log(engineer);
// Employee {
//   name: '凌凌漆',
//   dept: 'Javascript',
//   salary: 8000,
//   reports: [],
//   desktop_id: '007' }

关于Obejct.call(this),举例理解

function Manager() {
  Employee.call(this);
  this.reports = [];
  }

当使用new来创建一个Manager实例对象的时候,Manager里面的this指向的就是Manager实例。call(this)其实就是使用指定的上下文来调动函数,这里调用的是Employee的构造函数。换句话说,call(this)允许指定函数运行时,其内部的this指向哪个对象。当我们创建Manager对象时,this就指向了Manager的实例对象,因此

function Employee() {
  this.name = "";
  this.dept = "None";
  this.salary = 0.00;
  }

Employee中的this指向的是Manager对象,通过如此,Employee的属性就绑定到了Manager对象上了,构成了继承的效果。其实这一操作就相当于java中的super()

关于Manager.prototype = Object.create(Employee.prototype);。我们前面是使用的new来创建一个父对象,将父对象复制给子构造函数的prototype属性,如此来完成继承。

但是,考虑到调用父构造函数时,其内部的逻辑会被执行,有时候我们不需要执行这些内部逻辑,因此我们希望在继承的时候这些逻辑不被执行,而Manager.prototype = Object.create(Employee.prototype);就满足这样的要求。不需要调用构造函数,通过将父构造函数的原型传给Object.create也可以完成原型链的创建。

回顾经典

这里写图片描述

这里写图片描述

这里写代码片

猜你喜欢

转载自blog.csdn.net/w_bu_neng_ku/article/details/80282794
今日推荐