js面向对象

95 阅读7分钟

原型链

  • 实例对象与原型之间的连接,叫原型链
  • \_proto\_(隐式连接)
  • Object对象是原型链的最外层
function AAA(){
   this.name = "小明";
}
AAA.prototype.showName = function(){
   console.log(this.name);
}
var a = new AAA();
a.showName(); //小明
  • 根据一些数组的方法,可以推断出js源码中,系统对象也是基于原型的程序。
  • 尽量不要去修改或添加系统对象下的方法和属性
var arr = [1,2,3];
Array.prototype.push = function(){}
arr.push(4,5,6);
console.log(arr); //[1,2,3]
var arr = [1,2,3];
Array.prototype.push = function(){
    // this:1,2,3
    // arguments:4,5,6
    for (var i = 0; i < arguments.length; i++) {
      this[this.length] = arguments[i];
    }
    return this.length;
}
arr.push(4,5,6);
console.log(arr); //[1,2,3,4,5,6]

对象的组成

  • 方法(操作、行为)—— 函数:过程、动态的
  • 属性 —— 变量:状态、静态的

工厂方式 --- 面向对象中的封装函数

  • 改成与系统对象类似写法:首字母大写、New关键字提取、 This指向为新创建的对象
  • 构造函数
  • 存在的问题:对象的引用,浪费内存
  • 当new去调用一个函数:这个时候函数中的this就是创建出来的对象,而函数的返回值直接就是this(隐式返回)
  • new后面调用的函数:叫做构造函数
function CreatePerson(name){
    this.name = name;
    this.showName = function(){
      console.log(this.name);
    }
}
var p1 = new CreatePerson('小明');
p1.showName(); //小明
var p2 = new CreatePerson('小强');
p2.showName(); //小强

基本类型,赋值的时候只是值的复制
对象类型,赋值不仅是值的复制,而且也是引用的传递

var arr1 = [1,2,3],
    arr2 = [1,2,3];
console.log(arr1==arr2); //false
var a = [1,2,3];
var b = a;
b.push(4);
console.log(a); //[1,2,3,4]
var a = [1,2,3];
var b = a;
b = [1,2,3,4]; //内存中是直接重新给b一个新的内存指向
console.log(a); //[1,2,3]

原型 prototype

  • 原型可以用来重写对象方法,让相同方法在内存中存在一份(提高性能)
  • 通过原型改写工厂方式:原则:相同的属性和方法可以加在原型上、混合的编程模式
  • 总结面向对象写法:构造函数+属性,原型+方法

原型:去改写对象下面公用的属性或方法,让公用的属性或方法在内存中存在一份(提高性能)

var arr = [1,2,3,4,5],
    arr2 = [2,2,2,2,2];
Array.prototype.sum = function(){
  var result = 0;
  for (var i = 0; i < this.length; i++) {
    result += this[i];
  }
  return result;
}
console.log(arr.sum()); //15
console.log(arr2.sum()); //10
  • 关于优先级
var arr = [];
arr.number = 10;
Array.prototype.number = 20;
console.log(arr.number); //10
function CreatePerson(name) {
   this.name = name;
}
CreatePerson.prototype.showName = function(){
   console.log(this.name);
}
var p1 = new CreatePerson('小明'); //小明
var p2 = new CreatePerson('小强'); //小强
console.log(p1.showName()==p2.showName()); //true
console.log(p1.showName()===p2.showName()); //true

关于this指向:
情形1:

oDiv.onclick = function(){  
  this:oDiv //因为调用方式是oDiv.onclick()
}

情形2:

oDiv.onclick = show;
function show(){
  this:oDiv //因为调用方式是oDiv.onclick()
}

情形3:

oDiv.onclick = function(){
  show();
}
function show(){
  thiswindow
}

包装对象

  • js,基于原型
  • String Number Boolean
var str = new String('hello');
console.log(typeof str); //Object
var str = 'hello';
console.log(str.charAt(0));//基本类型会找到对应的包装类型对象,然后包装对象把所有的属性和方法给基本类型,然后包装对象消失
//原型中是String.prototype.charAt = function(){……}
//用原型方式封装一个返回最后一个字母的函数
var str = 'hello';
String.prototype.lastValue = function(){
  return this.charAt(this.length-1);
}
console.log(str.lastValue()); //o
var str = 'hello';
str.number = 10;
console.log(str.number); //undefined
			 //因为String类型下并没有number属性
var str = 'hello';
String.prototype.number = 20;
str.number = 10;
console.log(str.number);  //20
			 //string是基本类型,给基本类型属性是无效的
			 //js基本类型:string boolean number
  • 原型链的逐层寻找(直接对象--原型--object)
function Aaa(){
}
Aaa.prototype.number = 10;
var a1 = new Aaa();
console.log(a1.number); //10
function Aaa(){
    this.number = 20;
}
Aaa.prototype.number = 10;
var a1 = new Aaa();
console.log(a1.number); //20
function Aaa(){
}
Object.prototype.number = 30;
var a = new Aaa();
console.log(a.number); //30

原型链查找示意图:

  • 面向对象的一些属性和方法:
    hasOwnProperty 看是不是对象自身下的属性
    constructor 查找对象的构造函数
每个原型都会自动添加constructor属性
For in时有些属性是找不到的
避免修改constructor属性
  • instanceof运算符
对象与构造函数在原型链上是否存在关系
  • toString() Object上的方法
function Aaa(){}
var a1 = new Aaa();
console.log(a1.hasOwnProperty);
console.log(a1.hasOwnProperty==Object.prototype.hasOwnProperty); //true

a1.hasOwnProperty打印结果为:

var arr = [];
arr.num = 10;
Array.prototype.num2 = 20;
console.log(arr.hasOwnProperty('num')); //true
console.log(arr.hasOwnProperty('num2')); //false
function Aaa(){}
var a = new Aaa();
console.log(a.constructor); //ƒ Aaa(){}
// Aaa.prototype.constructor = Aaa; //每个函数都会有,系统自动生成
var arr = [];
console.log(arr.constructor); //ƒ Array() { [native code] }
console.log(arr.constructor==Array); //true
console.log(arr.constructor===Array); //true
function Aaa(){}
Aaa.prototype = {
    constructor: Aaa, 
    age: 20,
    name: '小明'
};
var a = new Aaa();
console.log(a.constructor); //ƒ Aaa(){}
function Aaa(){}
Aaa.prototype = {
    age: 20,
    name: '小明'
};
var a = new Aaa();
console.log(a.constructor); //ƒ Object() { [native code] }
function Aaa(){}
var a = new Aaa();
console.log(a instanceof Aaa); //true
console.log(a instanceof Array); //false
console.log(a instanceof Object); //true
  • toString 系统对象下面都是自带的,自己写的对象则是通过原型链找object下面的
var arr = [];
console.log(arr.toString()==Object.prototype.toString()); //false
function Aaa(){}
var a1 = new Aaa();
console.log(a1.toString()==Object.prototype.toString()); //true
var arr = [1,2,3];
console.log(arr.toString()); //1,2,3
Array.prototype.toString = function(){
    return this.join('+');
}
console.log(arr.toString()); //1+2+3
  • 利用toString做类型判断
var arr = [];
console.log(Object.prototype.toString.call(arr)); //[object Array]
console.log(Object.prototype.toString.call(arr)=='[object Array]'); //true
var re = new RegExp();
console.log(Object.prototype.toString.call(re)); //[object RegExp]
var a = null;
console.log(Object.prototype.toString.call(a)); //[object Null]
  • constructor instanceof在跨页面时(比如iframe),不能正确判断对象类型,所以最好用toString方式做类型判断
var oF = document.createElement('iframe');
document.body.appendChild(oF);
var ifArray = window.frames[0].Array;
var arr = new ifArray();
console.log(arr.constructor==Array);//false
console.log(arr instanceof Array);//false
console.log(Object.prototype.toString.call(arr)=='[object Array]');//true

对象的继承

继承

  • 在原有对象的基础上略作修改,得到一个新的对象
  • 不能影响原有对象的功能
  • 子类不影响父类,子类可以继承父类的一些功能(代码复用)

如何继承

  • 属性:调用父类构造方法 call
  • 方法:for in(拷贝继承)(jq也是采用拷贝继承extend)
  function CreatePerson(name,sex){ //父类
    this.name = name;
    this.sex = sex;
  }
  CreatePerson.prototype.showName = function(){
    console.log(this.name);
  }
  var p1 = new CreatePerson('小明','男');
  function CreateStar(name,sex,job){ //子类
    CreatePerson.call(this,name,sex);//使用call继承父类属性
    this.job = job;
  }
  CreateStar.prototype = CreatePerson.prototype;//这种继承父类方法的方式会影响到父类
  CreateStar.prototype.showJob = function(){}
  var p2 = new CreateStar('黄晓明','男','演员');
  console.log(p1);
  console.log(p2);

CreateStar.prototype = CreatePerson.prototype;这种方式去继承父类方法会对父类方法产生影响,之前知道for in可以实现对原对象无影响的拷贝,如:

  var a = {
    name: 'Alice'
  };
  var b = {};
  for (var attr in a) {
    b[attr] = a[attr];
  }
  b.name = 'Bob';
  console.log(a.name);//Alice
  console.log(b.name);//Bob

所以在继承父类方法时使用封装的这种for in方法:

  function extend(obj1,obj2){
    for (var attr in obj1) {
      obj2[attr] = obj1[attr];
    }
  }
  function CreatePerson(name,sex){ //父类
    this.name = name;
    this.sex = sex;
  }
  CreatePerson.prototype.showName = function(){
    console.log(this.name);
  }
  var p1 = new CreatePerson('小明','男');
  function CreateStar(name,sex,job){ //子类
    CreatePerson.call(this,name,sex);//使用call继承父类属性
    this.job = job;
  }
  extend(CreatePerson.prototype,CreateStar.prototype);//使用for in继承父类方法
  CreateStar.prototype.showJob = function(){}
  var p2 = new CreateStar('黄晓明','男','演员');
  console.log(p1);
  console.log(p2);

继承的其他形式

  • 原型继承——借助原型来实现对象继承对象
  • 类式继承——利用构造函数(类)继承的方式
  function Aaa(){ //父类
    this.name = '小明';
    this.arr = [1,2,3];
  }
  Aaa.prototype.showName = function(){
    console.log(this.name);
  }
  function Bbb(){ //子类
  }
  Bbb.prototype = new Aaa();//----------------------------类式继承实现句1
  var b1 = new Bbb();
  b1.showName(); //小明
  console.log(b1.name); //小明
  console.log(b1.constructor); //function Aaa(){ //父类
                                //this.name = '小明';
                                //this.arr = [1,2,3];
                               //}
  //所以要修正指向问题
  Bbb.prototype.constructor = Bbb;//-----------------------类式继承实现句2
  console.log(b1.constructor); //function Bbb(){ //子类
                               //}
  b1.arr.push(4);
  console.log(b1.arr);//[1,2,3,4]
  var b2 = new Bbb();
  console.log(b2.arr);//[1,2,3,4]
  //所以要修正影响继承属性的问题

实现类式继承:(属性和方法分开继承)
关于原型继承:
综上,继承的三种方式:拷贝继承类式继承原型继承

实例:拖拽

/** 
#div1 {
  position: absolute;
  width: 100px;
  height: 100px;
  background-color: pink;
}
<div id=“div1"></div>
**/
window.onload = function(){
  var oDiv = document.getElementById("div1");
  var disX = 0,
      disY = 0;
  oDiv.onmousedown = function(ev){
    var ev = ev||window.event;
    disX = ev.clientX-oDiv.offsetLeft;
    disY = ev.clientY - oDiv.offsetTop;
    document.onmousemove = function(ev){
      var ev = ev||window.event;
      oDiv.style.left = ev.clientX - disX + 'px';
      oDiv.style.top = ev.clientY - disY + 'px';
      console.log(oDiv);
    }
    document.onmouseup = function(){
      document.onmousemove = null;
      document.onmouseup = null;
    }
    return false; //阻止默认行为
  }
}
  • 改为面向对象的方式写法:
function Drag(id){
  this.oDiv = document.getElementById(id);
  this.disX = 0;
  this.disY = 0;
}
Drag.prototype.init = function(){
  var This = this;
  this.oDiv.onmousedown = function(ev){ //ev是事件对象里的,必须放在事件函数中
    var ev = ev||window.ev;
    This.fnMove(ev);
    return false; //阻止默认行为也是必须放在事件函数中
  }
}
Drag.prototype.fnMove = function(ev){
  var This = this;
  This.disX = ev.clientX - This.oDiv.offsetLeft;
  This.disY = ev.clientY - This.oDiv.offsetTop;
  This.oDiv.onmousemove = function(ev){
    var ev = ev||window.ev;
    this.style.left = ev.clientX - This.disX + 'px';
    this.style.top = ev.clientY - This.disY + 'px';
    this.onmouseup = function(){
      this.onmousemove = null;
      this.onmouseup = null;
    }
  }
}
window.onload = function(){
  var oDiv = new Drag("div1");
  oDiv.init();
}