JavaScript(8) | 面向对象

240 阅读11分钟

面向对象

1. 对象

一、对象简介

1、对象概念

对象属于一种复合的数据类型,在对象中可以保存多个数据类型的属性

2、对象分类

1)、内建对象: 由ES标准中定义的对象,在任何ES环境中都可以使用 如 Math , String , Number, Boolean, Function

2)、宿主对象: 由js运行环境提供的对象,目前主要指的是浏览器中提供的对象 如Bom,Dom分别是浏览器对象,文档对象。还有常见的对象 console.log,Document

3)、自定义的对象:这个最难,因为其他的都已经规定好的了,主要学习的也是这个,由开发人员自主创建 。

二、创建对象

使用new调用的函数是构造对象函数 construcor构造函数时专门创建对象的函数。

格式:

var 对象变量名 = new Object() ;

实例:

var   obj =  new Object  () ; 
console.log( obj) ; 

控制台输出:

{}[[Prototype]]: Object

分析: new Object() 表示创建一个对象( 注意Object的O是大写 ),给对象实例化一个变量obj。此时的对象只是一个空对象,变量没有实例化。

三、对象属性

1、基本概念

在对象中保存的值被称为属性

格式:

对象.属性名 = 属性值 ;

实例:

var obj.name = "孙悟空" ;

分析: 同样的,属性实质表示是具体内容,属性名就是变量是一个容器,而属性值才是真正的内容,相当于字面量。从面向对象角度来看,属性就是属性值的「抽象」,而属性值就是属性的「具体」。

2、读取属性

概念: 读取属性就是获取属性变量中的属性值。

格式:

对象.属性名

实例:

// 输出 孙悟空
console.log( obj.name ); // 孙悟空

分析: 使用「.」来访问对象的属性值。修改属性值 相当于重新给属性赋值。

3、删除属性

格式:

delete obj.name

实例:

var obj = new Object() ; // 使用new的对象都是大写开头
obj.name = "孙悟空" ;
console.log("删除前name: " + obj.name) ; // 删除前name: 孙悟空
delete obj.name
console.log("删除后name: " + obj.name) ; //删除后name: undefined

分析: delete obj.name 之前有属性名变量和属性值,使用delete后,没有这个obj的属性值,所以值为 undefined,但属性名变量存在。

4、属性变量的表示

为了表示属性(变量)有很多方式

对象的属性变量名不强制要求标识符命名规则,但是如果需要使用特殊的名字,不能采用 .的方式访问属性值,需采用对象名["属性名"]的方式。

格式:

var 对象变量名 = new Object() ;
// 访问 修改 属性值
对象变量名[属性名] = 新属性值 ;

实例:

var obj = new Object();
obj["名字"] = "孙悟空";
console.log(obj["名字"]); // 孙悟空

分析: 那么此时的名字是属性名,而孙悟空是属性值。

与默认使用的添加属性值的方法相比,这个方法 [ ]中的属性名还可以是变量,这样使用起来也更加灵活。js 对象的属性名, 可以是任意数据类型(包括对象)

四、IN运算符

作用: 可以检查某对象是否包含指定属性,如果有则返回true,否则返回flase。

格式:

属性 in 对象变量

实例:

//检查 obj 中是否含有 test1 属性
var obj = new Object();
obj["名字"] = "孙悟空";
console.log(obj["名字"]); // 孙悟空
console.log(  "名字"  in  obj); // true
console.log(  "年龄"  in  obj); // false

五、数据类型和引用类型区别

1、基本数据类型

基本数据类型的值直接保存在 栈内存中,值与值之间是相互独立的,修改一个变量不会影响另一个变量。

var a = 123;
var b = a;
console.log("b的值: " + b); // b的值: 123

分析: a先赋值123,然后b赋值a的值。b的123和a的123没有任何关系,只是看起来值 相同,是相互独立的。

image.png

2、引用数据类型(对象)

var obj1 = new Object();
var obj2 = new Object();
obj1.name = "孙悟空" ;
obj2 = obj1;
console.log("obj1的name: " + obj1.name ) ; // obj1的name: 孙悟空
console.log("obj12的name: " + obj2.name ) ; // obj12的name: 孙悟空
// 修改 obj2的name
obj2.name = "猪八戒" ;
console.log("obj1的name: " + obj1.name ) ; // obj1的name: 猪八戒
console.log("obj12的name: " + obj2.name ) ; // obj12的name: 猪八戒

分析: 修改 obj2的name值,obj1的name也跟着改变了,原因是obj1和obj2在内存中指向的是同一块区域。

image.png

第一次创建obj1对象时,实际是开辟一个空间,将空间的地址0x99赋值给一个变量,作为对象变量obj1。当执行obj2 = obj1; 时,将变量obj1的值0x99 “传递” 给 obj2, 这样导致 obj1 和 obj2 指向同一块内存空间。当执行obj2.name = "猪八戒" ;时,这个「猪八戒」实质赋值给了 0x99所指向的内存空间 的name变量,这样 obj1 和obj2指向的内存空间的name属性 值都是 「猪八戒」了。

3、 创建对象另一种方式

创建对象的另一种方式: 使用对象的字面量 创建对象。

格式:

// 使用 字面量方式创建 对象
var 属性变量 = {};
// 使用 new 创建对象
var 属性变量 = new Object();

实例:

// 创建
var obj1 = {} ;
// 创建并赋值
var obj2 = {name:"猪八戒"};
console.log("obj2的名字: " + obj2.name); // obj2的名字: 猪八戒

分析: 两种方式本质是一样的。但是使用对象的字面量使用方便一些创建对象的时候就可以给属性赋值。若有多个属性则属性「名-值对」之间用 ,号隔开。开发中常用 对象字面量的方法 创建对象


2. 方法

1、方法概念

方法是站在面向对象的角度的“函数”。

2、使用方法

// 创建对象
var obj = new Object ()  ;

// 向对象添加属性
obj.name  =  "孙悟空";
obj.age  = 18;

对象中的属性可以是任意的类型,其中特殊的时对象,函数...。

obj.sayName = function ( ) {
   console.log( obj.name  );
};

此时对象的属性就是一个函数。使用函数对象(有名函数)方式的一个用处是为了 调用函数

格式:

对象.方法名();

实例:

obj.sayName( );
// 创建对象
var obj = new Object();

// 向对象添加属性 (基本类型)
obj.name = "孙悟空";
obj.age = 18;

// 向对象添加属性 (函数)
obj.sayName = function () {
    console.log("name: " +  obj.name);
};
// 使用方法
obj.sayName(); // name: 孙悟空

分析: 对象使用自己的方法sayName打印出 自己的属性name。这里的创建函数的形式是 变量式

用普通的函数形式的调用函数 ,其实也有一个对象,这个对象就是 window,当然上面例子的函数是赋值给一个对象的(实际上是给对象的属性🤣),那么这个函数对象就是这个obj啦。

如果一个函数是一个对象的属性(像上例子一样),那么。那么把这个函数称为这个对象的方法( method )


3. 枚举对象中的属性

概念: 枚举对象就是用字面量形式创建对象,形式上是「名值对」,格式整齐,一枚一枚的。

// 用函数 字面量 创建 函数
var obj = {
   name : "孙悟空",
   age : 18,
   gender : "男",
   address : "花果山"
};

🚀T-测试遍历输出枚举对象的属性

// 用函数 字面量 创建 函数
var obj = {
    name: "孙悟空",
    age: 18,
    gender: "男",
    address: "花果山",
};
// for 循环遍历输出枚举对象 属性
for(var n in obj) {
    console.log(n);
}

控制台输出结果:

name
age
gender
address

分析: 这里使用for in 语句对“集合”形式的内容遍历。

🚀T-测试遍历输出枚举对象的属性值

// 用函数 字面量 创建 函数
var obj = {
    name: "孙悟空",
    age: 18,
    gender: "男",
    address: "花果山",
};
// for 循环遍历输出枚举对象 属性
for(var n in obj) {
    console.log(obj[n]);
}

控制台输出结果:

孙悟空
18
男
花果山

分析: for in 循环特点, n代表obj属性集合中的某个属性,是一个变量,每次一轮循环属性就变成下一个属性,使用obj[属性]形式获取属性值。

window对象 由浏览器创建 在全局作用域中创建的方法,函数都可以是window对象的属性,方法。全局的函数会作为window的方法储存


4. 提前声明现象

1、变量声明提前

情况1: 先用var 定义变量a赋值,后使用console.log()打印a的值。

var a = 3;
console.log("a = " + a ); // a = 3

分析: 当按照先声明var,再赋值的方式,没有错误

情况2: 调换上下语句。当var声明变量放在调用此变量语句的后面,会出现奇怪效果。

console.log("a = " + a ); // a = undefined
var a = 3;

效果: 变量a没有出错,但是值为 undefined

分析:

情况3: 在情况2基础上去除var声明 ( 当不使用var声明变量也放在使用变量语句的后面 )。

console.log("a = " + a ); // a = undefined
a = 3;

效果:

image.png

分析: 当变量声明的语句部分在变量使用语句的后面(下方)时,程序先执行有var的语句,但是不赋值,所以其情况2的值为 undefined。而情况3去掉var后,变量a就没有定义,声明,即a是非法的,就会出错。

理解: 可以理解为 编译器会先预处理,把 var "浮"在上面。但变量要遵循先声明赋值,后使用的原则 (由上到下)。

编译器没有"看到"var,则执行变量的使用语句时,变量还未声明,不认识,即报错。

2. 函数声明提前

和变量是一样的,大同小异

var a = 3;
var b = 4;

fun1();
function fun1(){
    console.log("a = " + a);
}

fun2();
var fun2 = function(){
    console.log("b = " + b);
}

效果:

image.png

(完)


5. 上下文对象this

image.png


6. 使用工厂方法创对象

1、工厂方法简介

概念: 工厂方法就是创建一个“模板”完成重复性,有个别参数不同的任务功能。

目的: 创建类

2、工厂方法创建

1)、不使用工厂方法创建对象( 字面量形式创建对象 )

var  obj  = {
    name  : "孙悟空",
    age  : 18,
    gender  : "男",
    address  : "花果山"
    sayName :  function( ) {

    };

分析: 这样使用字面量形式创建对象,可以在创建的同时给属性复制,非常灵活。但这种方式也存在问题.就是当有大量创建对象的需求时,这种方式显得非常繁琐。而实际上这样的对象,属性类型和属性个数是基本固定的,变化也只是体现在具体属性值,也就是创建一个新的对象的过程只需要改变属性值。像这种有着重复结构,但是固定位置的内容不同可以考虑封装为函数,批量地处理,提升效率。

2)、使用工厂方法批量创建对象

function creartePerson(){
	// 函数体
    // 创建对象功能语句
    // 返回对象
    return obj;
}

creartePerson();

分析: 想实现这样的效果。使用creartePerson();传递实参,在函数内部完成结构的建立和属性值的赋值。

完成函数体

function createPerson ( name ,age ,gender ,address ) { 
	var obj = new Object();
	obj.name = name;
	obj.age =age;
	obj.gender = gender ;
	obj.address =address;
	obj.sayName = function () { 
	alert(this.name);
   }
return obj;
};

createPerson(); //调用的时候可以传递不一样的参数值

这个createPerson函数是创建对象的工厂,obj是模板,模板也就是


7. 构造函数

1、使用构造函数创建对象

使用:

// 创建模板
function Person(){ // 这个Person相当于一个这是一类事物
   console.log(this);
}


// 创建对象psn
var psn = new Person(); //这个per相当于一个实例

控制台输出:

Person{}
   _proto_:Object

分析: 此时的this 就是 psn

构造函数执行流程:

  1. 构造函数创新对象
  2. 新对象设为函数this
  3. 逐行执行代码
  4. 新建对象作返回值

所以之前的函数的构建中创建函数对象,用 new这种方式其实是构造函数。

3、instanceof

概念: 使用 instanceof 可以判断一个对象是否是一个类的实例

// 创建构造方法
function Person(){
    console.log(this);
}

// 创建对象
var psn = new Person();

// 判断psn是否属于 Person类
console.log(psn instanceof Person);

效果:

image.png

注意事项和细节

  • 构造函数首字母大写
  • 普通函数直接调用
  • 构造函数使用 New 关键字调用
  • Object是所有的类的父类

4、构造函数修改

构造函数好是好,但是也存在一些问题。

构造函数中的方法是 每执行一次就要创建一个里面的新的方法,如果执行 10000次 ,也就是要创建10000个方法,关键是这些方法都是相同的。所以能不能利用这些一样的方法,使之称为共享的方法

1)、使方法公共化: 将方法写入的到全局作用域中

  • 将函数定义到全局作用域中的话,会污染全局作用域的空间。
  • 不安全, 同一个函数名,容易被改写,覆盖。

8. 原型对象

引入一个概念原型

暂略...

9. toString() 方法

toSting( )是原型的方法 ,可以被对象重写

暂略...

10. 垃圾回收机制

将不适用的对象设为 null

暂略...

(完)