JavaScript 面向对象开发详解以及垃圾回收

面向对象的概述

ECMAScript 有两种开发模式:1.函数式(过程化),2.面向对象(OOP)。

面向对象的语言有一个标志,那就是类的概念,而通过类可以创建任意多个具有相同属性和方法的对象。但是,传统ECMAScript中 没有类的概念(ES6开始有),因此它的对象也与基于类的语言中的对象有所不同。

js是基于对象,不是面向对象的。不具备描述事物的能力。

 

 

创建对象的方式—new Object

创建一个对象,然后给这个对象新建属性和方法

如果一个函数作为一个对象的属性保存,那么我们称这个函数时这个对象的方法。调用这个函数就说调用对象的方法(method)

var box = new Object();             //创建一个 Object 对象
box.name = 'Lee';  //box对象中创建一个 name 属性并赋值
box.age = 100;     //box对象中创建一个 age 属性并赋值
//box对象中创建一个 run()方法并返回值
box.run = function () {
  return this.name + this.age + '运行中...';
};
console.log(box.run());   //调用box对象中的run方法

上面创建了一个对象,并且创建属性和方法,在 run()方法里的 this,就是代表 box 对象本身。

这种是 JavaScript 创建对象最基本的方法,但有个缺点,想创建一个类似的对象,就会产生大量的代码。

var box = new Object();
box.name = 'Lee';
box.age = 100;
box.run = function () {
  return this.name + this.age + '运行中...';
};
console.log(box.run());

var box2 = box; //得到 box 的引用
box2.name = 'Jack';//直接改变了 name 属性
console.log(box2.run());
console.log(box.run()); //用 box.run()发现 name 也改变了


var box3 = new Object();
box3.name = 'Jack';
box3.age = 200;
box3.run = function () {
  return this.name + this.age + '运行中...';
};
console.log(box3.run());  //这样才避免和 box 混淆,从而保持独立

为了解决多个类似对象声明的问题,我们可以使用一种叫做工厂模式的方法,这种方法就是为了解决实例化对象产生大量重复的问题

 

 

创建对象的方式—工厂模式

function createObject(name, age) { //集中实例化的函数
  var obj = new Object();
  obj.name = name;
  obj.age = age;
  obj.run = function () {
    return this.name + this.age + '运行中...';
  };
  return obj;
}
var box1 = createObject('Lee', 100); //第一个实例
var box2 = createObject('Jack', 200); //第二个实例
console.log(box1.run()); // Lee100运行中...
console.log(box2.run()); //保持独立:Jack200运行中...

工厂模式解决了重复实例化的问题,但还有一个问题,那就是识别问题,因为根本无法搞清楚他们到底是哪个对象的实例。

function createObject(name, age) { //集中实例化的函数
  var obj = new Object();
  obj.name = name;
  obj.age = age;
  obj.run = function () {
    return this.name + this.age + '运行中...';
  };
  return obj;
}
var box1 = createObject('Lee', 100); //第一个实例
var box2 = createObject('Jack', 200); //第二个实例
console.log(box1.run());
console.log(box2.run()); //保持独立

console.log(typeof box1); //Object
//console.log(box1 instanceof box);//不能判断
console.log(box1 instanceof Object); //true    都是Object类型 无法区分谁到底是谁的

 

 

创建对象的方式—构造函数

ECMAScript 中可以采用构造函数(构造方法)可用来创建特定的对象。类型于 Object 对象(用函数来模拟面对对象的中的描述)

//用js来描述人
function Person(){                    //相当于构造器。
  console.log("person run");
}

//通过描述进行对象的建立。 new.
var p  = new Person(); // 这里创建Person对象的时候就会执行构造器里面的代码:person run
//动态给p对象添加属性。直接使用p.属性名即可。
p.name = "zhangsan";
p.age = 29;
//如果定义的p对象的属性赋值为一个函数,即是给p对象添加一个方法。
p.show = function(){
  console.log("show :"+this.name+":"+this.age);
}

p.show(); //show :zhangsan:29

使用构造函数的方法,即解决了重复实例化的问题,又解决了对象识别的问题,但问题是,这里并没有 new Object(),为什么可以实例化 Box(),这个是哪里来的呢

构造函数的方法,和使用工厂模式的方法他们不同之处

构造函数方法没有显示的创建对象(new Object());但它后台会自动var object = new Object();

直接将属性和方法赋值给 this 对象;this就相当于object

没有 renturn 语句,构造函数不需要返回对象的引用,它是后台自动返回的

构造函数的方法有一些规范

函数名和实例化构造名相同且大写,(PS:非强制,但这么写有助于区分构造函数和普通函数);

通过构造函数创建对象,必须使用 new 运算符。

通过构造函数创建对象执行的过程

既然通过构造函数可以创建对象,那么这个对象是哪里来的,new Object()在什么地方执行了?执行的过程如下:

当使用了构造函数,并且 new 构造函数(),那么就后台执行了 new Object();

将构造函数的作用域给新对象,(即 new Object()创建出的对象),而函数体内的 this 就代表 new Object()出来的对象。

执行构造函数内的代码;

返回新对象(后台直接返回)。

关于 this 的使用

this 其实就是代表当前作用域对象的引用。如果在全局范围 this 就代表 window 对象,如果在构造函数体内,就代表当前的构造函数所声明的对象。

var box = 2;
alert(this.box); //全局,代表 window

构造函数和普通函数的唯一区别,就是他们调用的方式不同。只不过,构造函数也是函数,必须用 new 运算符来调用,否则就是普通函数(函数名首字母无序大写)。

function Box(name, age) { //构造函数模式
  this.name = name;
  this.age = age;
  this.run = function () {
    return this.name + this.age + '运行中...';
  };
}

var box = new Box('Lee', 100); //构造模式调用
console.log(box.run());
Box('Lee', 20); //普通模式调用,无效

可以使用对象冒充来调用

function Box(name, age) { //构造函数模式
  this.name = name;
  this.age = age;
  this.run = function () {
    return this.name + this.age + '运行中...';
  };
}

var o = new Object();
Box.call(o, 'Jack', 200) //对象冒充调用,o这个对象冒充Box对象来运行
console.log(o.run());

探讨构造函数内部的方法(或函数)的问题,首先看下两个实例化后的属性或方法是否相等。

function Box(name, age) { //构造函数模式
  this.name = name;
  this.age = age;
  this.run = function () {
    return this.name + this.age + '运行中...';
  };
}
var box1 = new Box('Lee', 100); //传递一致
var box2 = new Box('Lee', 100); //同上

console.log(box1.name == box2.name); //true,属性的值相等
console.log(box1.run == box2.run); //false,方法其实也是一种引用地址
console.log(box1.run() == box2.run()); //true,方法的值相等,因为传参一致

可以把构造函数里的方法(或函数)用 new Function()方法来代替,得到一样的效果,更加证明,他们最终判断的是引用地址,唯一性。

function Box(name, age) {                     //new Function()唯一性
  this.name = name;
  this.age = age;
  this.run = new Function("return this.name + this.age + '运行中...'");
}
var box1 = new Box('Lee', 100); //传递一致
var box2 = new Box('Lee', 100); //同上

console.log(box1.name == box2.name); //true,属性的值相等
console.log(box1.run == box2.run); //false,方法其实也是一种引用地址
console.log(box1.run() == box2.run()); //true,方法的值相等,因为传参一致

我们可以通过构造函数外面绑定同一个函数的方法来保证引用地址的一致性,但这种做法没什么必要,只是加深学习了解:

function Box(name, age) {
  this.name = name;
  this.age = age;
  this.run = run;
}
function run() { //通过外面调用,保证引用地址一致
  return this.name + this.age + '运行中...';
}
var box1 = new Box('Lee', 100); //传递一致
var box2 = new Box('Lee', 100); //同上

console.log(box1.name == box2.name); //true,属性的值相等
console.log(box1.run == box2.run); //true,用的是同一个函数,所以地址相同
console.log(box1.run() == box2.run()); //true,方法的值相等,因为传参一致

虽然使用了全局的函数 run()来解决了保证引用地址一致的问题,但这种方式又带来了一个新的问题,全局中的 this 在对象调用的时候是 Box 本身,而当作普通函数调用的时候,this 又代表 window。

 

 

创建对象的方式—构造函数+原型组合方式

构造函数+原型组合方式:自定义构造函数, 属性在函数中初始化, 方法添加到原型上

适用场景: 需要创建多个类型确定的对象

function Person (name, age) {
    this.name = name
    this.age = age
  }
  Person.prototype.setName = function (name) {
    this.name = name
  }
  var p1 = new Person('Tom', 12)
  var p2 = new Person('JAck', 23)
  p1.setName('TOM3')
  console.log(p1)

  Person.prototype.setAge = function (age) {
    this.age = age
  }
  p1.setAge(23)
  console.log(p1.age)

  Person.prototype = {}
  p1.setAge(34)
  console.log(p1)
  var p3 = new Person('BOB', 12)
  p3.setAge(12)

 

 

内存问题(垃圾回收GC)

JavaScript 具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。其他语言比如 C 和 C++,必须手工跟踪内存使用情况,适时的释放,否则会造成很多问题。

JavaScript 则不需要这样,它会自行管理内存分配及无用内存的回收。我们需要做的只是要将不再使用的对象设置null即可

JavaScript 最常用的垃圾收集方式是标记清除。垃圾收集器会在运行的时候给存储在内存中的变量加上标记。然后,它会去掉环境中正在使用变量的标记,而没有被去掉标记的变量将被视为准备删除的变量。

最后,垃圾收集器完成内存清理工作,销毁那些带标记的值并回收他们所占用的内存空间。

垃圾收集器是周期性运行的,这样会导致整个程序的性能问题。比如 IE7 以前的版本,它的垃圾收集器是根据内存分配量运行的,比如 256 个变量就开始运行垃圾收集器,这样,就不得不频繁地运行,从而降低的性能。

一般来说,确保占用最少的内存可以让页面获得更好的性能。那么优化内存的最佳方案,就是一旦数据不再有用,那么将其设置为 null 来释放引用,这个做法叫做解除引用。这一做法适用于大多数全局变量和全局对象。

当一个对象没有任何的变量或属性对它进行引用,此时我们将永远无法操作该对象,此时这种对象就是一个垃圾,这种对象过多会占用大量的内存空间,导致程序运行变慢,所以这种垃圾必须进行清理。

var o = {
  name : 'Lee'
};
o = null; //解除对象引用,等待垃圾收集器回收

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值