对象
面向对象
面向对象分析
(1)OOA (Object oriented analysis)
面向对象分析方法是确定需求或者业务的角度,按照面向对象的思想来分析业务。例如:OOA只是对需求中描述的问题,进行模块化的处理,描述问题的本质,区别每个问题的不同点相同点,确定问题中的对象。OOA与结构化分析有较大的区别。OOA所强调的是在系统调查资料的基础上,针对OO方法所需要的素材进行的归类分析和整理,而不是对管理业务现状和方法的分析。
面向对象分析是一种分析方法。
面向对象设计
(2)OOD(Object oriented design)
面向对象设计方法是OO方法中一个中间过渡环节。其主要作用是对OOA分析的结果作进一步的规范化整理,以便能够被OOP直接接受。
面向对象设计是一种设计方法,包括面向对象分解的过程和一种表示法,这种表示法用于展现被设计系统的逻辑模型和物理模型、静态模型和动态模型。
这里定义两个要点:
1)面向对象设计导致了面向对象分解;
2)面向对象设计使用了不同的表示法来表达逻辑设计(类和对象结构)和物理设计(模块和处理架构)的不同模型,以及系统的静态和动态特征。
面向对象编程
(3)OOP(Object oriented programming)-
面向对象编程是一种计算机编程架构。OOP 的一条基本原则是计算机程序是由单个能够起到子程序作用的单元或对象组合而成。
面向对象编程是一种实现的方法,在这种方法中,程序被组织成许多组互相协作的对象,每个对象代表某个类的一个实例,而类则属于一个通过继承关系形成的层次结构。
面向对象编程技术的关键性观念是它将数据及对数据的操作行为放在一起,作为一个相互依存、不可分割的整体——对象。对于相同类型的对象进行分类、抽象后,得出共同的特征而形成了类。面向对象编程就是定义这些类。类是描述相同类型的对象集合。类定义好之后将作为数据类型用于创建类的对象。程序的执行表现为一组对象之间的交互通信。对象之间通过公共接口进行通信,从而完成系统功能。
这里定义有三个要点:
1)利用对象作为面向对象编程的基本逻辑构建块,而不是利用算法;
2)每个对象都是某个类的一个实例;
3)类与类之间可以通过继承等关系联系在一起。
关系
它们之间的关系基本上,面向对象分析的结果可以作为开始面向对象设计的模型,面向对象的设计结果可以作为蓝图,利用面向对象编程方法最终实现一个系统。
对象和类
前言叙述(对象,类)
我们想来思考一个问题,对象是什么。简言之,包含属性和方法的就是对象。属性是静态的,通常写在构造函数中,方法是动态的,公用的,通常写在原型中。更严肃一点即“类的实例”。
var obj = new Object( )
obj 是对象,Object 是类。
对象是类的实例和具体,类是对象的集合
类是确定对象将会拥有的特征(属性)和行为(方法),它不是具体客观存在的东西
举个例子:如果说类是女朋友的话,对象就是你的女朋友。
js中的四大内置对象,都是由类实例化的。(js四种内置对象分别为Math对象、Date对象、String对象以及Array对象)
以上由ECMAScript 6底层规定如此。
工厂模式
来看一个简单案例,我们要创建一些数据信息,比如创建员工的信息。笨拙的方法是一个函数一个函数写。
//定义第一个人
var obj1 = new Object(); obj1.name = "小刚"; obj1.sex = "男"; obj1.age = "24"; obj1.interest = "网上冲浪"; obj1.sayHello = function(){ alert(this.name); } //定义第二个人 var obj2 = new Object(); obj2.name = "小兰"; obj2.sex = "女"; obj2.age = "22"; obj2.interest = "吃火锅"; obj2.sayHello = function(){ alert(this.name); } //定义第三个人 var obj3 = new Object(); obj3.name = "小梦"; obj3.sex = "男"; obj3.age = "29"; obj3.interest = "发呆"; obj3.sayHello = function(){ alert(this.name); } //写不下去了。。。 obj1.sayHello(); obj2.sayHello(); obj3.sayHello(); //如果有1w个人的话,就要把以上代码写上上万遍。可摇了我吧。
属性值都是变化的,但方法是一样的。
让我们来创建一个工厂。
function factory(name,sex,age,interest){ var obj = new Object(name,sex,age,interest); obj.name = name; obj.sex = sex; obj.age = age; obj.interest = interest; obj.sayHello = function(){ alert(this.name); } return obj; } var obj1 = factory("小刚","男","24","网上冲浪"); var obj2 = factory("小兰","女","22","吃火锅"); var obj3 = factory("小梦","男","29","发呆"); 对比一下,通过封装,代码量减少了很多,整体也优雅了许多。这就是面向对象思想的一个小小体现。
构造函数
创建构造函数
new后面就是构造函数,构造函数中的this就是指向当前对象。
var arr = new Array( );
以上:
对象=》arr,类=》Array。对象是由类所构造的。
所以:
Array( ) 即为构造函数。
还是以上案例,让我们变形一下。
function Factory(name,sex,age,interest){ this.name = name; this.sex = sex; this.age = age; this.interest = interest; this.sayHello = function(){ alert("你好"+this.name); } } var obj1 = new Factory("小刚","男","24","网上冲浪");//this指向 obj1 var obj2 = new Factory("小兰","女","22","吃火锅");//this指向 obj2 var obj3 = new Factory("小梦","男","29","发呆");//this指向 obj3 obj1.sayHello(); obj2.sayHello(); obj3.sayHello(); //构造函数中,首字母要大写 obj1 是 对象; Factory 是 类; Factory() 是构造函数。
构造函数与普通函数区别
普通函数封装后可以再new成构造函数。
1、构造函数也是一个普通函数,创建方式和普通函数一样,但构造函数首字母大写
2、构造函数和普通函数的区别在于:调用方式不一样。作用也不一样(构造函数用来新建实例对象)
3、调用方式不一样。
a. 普通函数的调用方式:直接调用 person();
b.构造函数的调用方式:需要使用new关键字来调用 new Person();
4、构造函数的函数名与类名相同:Person( ) 这个构造函数,Person 既是函数名,也是这个对象的类名
5、内部用this 来构造属性和方法
function Person(name,job,age) { this.name=name; this.job=job; this.age=age; this.sayHi=function() { alert("Hi") } }
构造函数的执行流程
A、立刻在堆内存中创建一个新的对象
(1 栈:为编译器自动分配和释放,如函数参数、局部变量、临时变量等等
2 堆:为成员分配和释放,由程序员自己申请、自己释放。否则发生内存泄露。典型为使用new申请的堆内容。)
B、将新建的对象设置为函数中的this
C、逐个执行函数中的代码
D、将新建的对象作为返回值
6、普通函数例子:因为没有返回值,所以为undefined
7、构造函数例子:构造函数会马上创建一个新对象,并将该新对象作为返回值返回
8、用instanceof 可以检查一个对象是否是一个类的实例,是则返回true;
所有对象都是Object对象的后代,所以任何对象和Object做instanceof都会返回true
给对象[]直接添加属性
js中四大内置对象同理。es6出现之前,前端中并没有类的概念,只讲对象和实例,只能通过以上的方法模拟类。es6出现之后就统一了江湖。现在又出现==.vue==,.scss,.ts,mvc等,都是由后台模拟过来。例如Java之前需要编译城javac。我们vue编译成html,scss编译成css,ts编译成js,这些语言的思想都是相似的。
1
|
var a =[{name: 'Tom' ,age:20},{name: 'Tom2' ,age:22}] |
现在给a数组中的第一个对象添加性别属性
1
2
3
|
a[0][ 'gender' ]= 'women' a[0][ 'address' ]= "China" a[1].province= "Jiangsu" |
多添加了一些属性,是为了区别字符串单引号和双引号的,
用了.就不用中括号不用单引号
不用点 就要用中括号和单引号
上面是效果;
如果不小心,没有指定是数组中的第几个对象,直接对a数组符合,会如何?
1
|
a.hahaha=123 |
此时数组长度还是2,内容变成
如果我不是随意找一个hahaha,而是用了一个关键字呢,如下图,数组长度变成了3
1
|
a.push({name: 'Jack' }) |
如果希望删除Jack这个对象呢?
delete a[2]
对象之间拷贝
对象的拷贝
var a={
name:1,
name:2,
sex:3
}
var b={};
for(var key in a){
b[key]=a[key];//这里的key是变量 要把属性作为变量的话 可以用中括号这种方式
}
console.dir(b);
封装函数
function copy(o1,o2)
{
for (var key in o1){
o2[key]=o1[key]
}
}
copy(object1,object2);
浅拷贝
我们把一个对象的成员复制给另一个对象成员的时候,只能把第一层的值复制过去,如果属性又是一个对象,它复制的只是对象的引用,而没有新的创建对象
var a={
name:1,
name:2,
sex:3
dog{
name:4,
age:5,
yellow:6
}
}
var b={};
for(var key in a){
b[key]=a[key];//这里的key是变量 要把属性作为变量的话 可以用中括号这种方式
}
console.dir(b);
这里的dog属性会被拷贝,但是a如果改变了dog属性,b中的dog属性会跟着变化
因为dog对象有独立的内存空间,dog属性只是指向了dog地址
深拷贝
多层复制,把属性对应的对象也拷贝了一份
var a={
name:1,
age:2,
agree:3,
dog:{
first:4,
twice:5
},
friend:["ls","ww"]
}
var b={};
function copy(o1,o2){
for(var key in o1){
var item=o1[key];
if(item instanceof Object){
o2[key]={};
copy(item,o2[key]);//这里的KEY是直接给o2创造了属性,相当于O2["key"]={}
}else if(item instanceof Array){
o2[key]=[];
copy(item,o2[key])
}else{
o2[key]=o1[key];
}
}
}
copy(a,b);
a.dog.first=6;
a.friend[0]="xx"
console.dir(a);
console.dir(b);
遍历DOM树
function loadtree(parent){
for (var i=0;i<parent.children.length;i++){
//遍历第一级子元素
var child=parent.children[i];
console.log(child);
//递归调用
loadtree(child);
}
}
应用
批量增加点击事件
function loadtree(parent,callback){//callback作为回调函数传进来 for (var i=0;i<parent.children.length;i++){ var child=parent.children[i]; if(callback){ callback(child); }
loadtree(child);
}
}
loadtree(document.getElementById("div1"),function(text){
text.onclick=function(){
console.log(this.innerText);
}
});
原型
原型是用于存放公用的方法和属性的,让公用的方法和属性在内存中存在一份,提升性能。写于构造函数下。每个构造函数都有一个属性叫原型或者叫原型对象
当调用对象的属性或者方法的时候,先去找对象本身的属性/方法,如果对象没有,去调用原型中的对象和方法
在原型对象中有一个属性 constructor 指向构造函数 作用记录了创建该对象的构造函数
通过构造函数创建的对象,可以访问构造函 数的原型中的成员(原型中的成员在内存中只会存储一份)
如果属性是在原型对象上(比如Student.prototype.text='demo'),而不是在对象上。对象设置属性的值的时候,不会搜索到原型链,而是直接给对象新增一个属性(比如s1.text=123,不会影响到原型链)
原型链
原型对象也是对象
所有对象都有toString()方法(可把一个 Number 对象转换为一个字符串,并返回结果,中间可选参数,转换为相应进制)
我们知道构造函数的prototype属性是用来指向原型对象的,现在重写Person.prototype属性意味着它指向了一个新的对象(称为新的原型对象),不再指向函数创建时生成的那个原型对象了
改变prototype时,需要重新设置constructor属性
先设置原型属性,再创建对象,才能访问原型对象中的成员
数组或者string中的prototype是不可以修改的,但是可以给原型添加成员 比如 Array.prototype.getSum=function(){....};
js就是基于原型的对象。
直接来看一段笨拙的数组求和的代码。
var arr1= [1,2,4]; var arr2 = [3,3,4,5,6,8,9]; //数组求和 var sum1 = 0; for(var i = 0;i<arr1.length; i++){ sum1 += arr1[i]; } console.log(sum1); var sum2 = 0; for(var i = 0;i<arr2.length; i++){ sum2 += arr2[i]; } console.log(sum2); 还是上面两个数组,再来封装一下 function sum(arr){ var sum = 0; for(var i = 0;i<arr.length; i++){ sum += arr[i]; } return sum; } console.log(sum(arr1)); console.log(sum(arr2)); //可以得到相同的结果
这是最普通的实现方式,下面让我们继续优化一下,使代码更加优雅。
还是上面两个数组。
Array.prototype.sum = function(){ //this指向当前对象,prototype是向对象添加属性的方法。语法是object.prototype.name=value var sum = 0; for(var i = 0;i<this.length;i++){ sum += this[i]; } return sum; } console.log(arr1.sum); console.log(arr2.sum);
//还是相同的结果
在这里,length便是公用的属性,求和就是方法。
基于此,未来出现数组求和都能实现。
再回到最开始关于添加人员的案例,我们只需要改动一句代码便可以使其变成原型。
原型写在构造函数后。
function Factory(name,sex,age,interest){ this.name = name; this.sex = sex; this.age = age; this.interest = interest; } Factory.prototype.sayHello = function(){ alert("你好"+this.name); } var obj1 = new Factory("小刚","男","24","网上冲浪");//this指向 obj1 var obj2 = new Factory("小兰","女","22","吃火锅");//this指向 obj2 var obj3 = new Factory("小梦","男","29","发呆");//this指向 obj3 obj1.sayHello(); obj2.sayHello(); obj3.sayHello();
自调用函数
一个单独的局部作用域。里面的函数不会被外部访问,避免了命名问题 ,除了使用window方法 ,自调用函数有个问题,因为以后js上传服务器都是压缩到一个js的,多个自调用函数混在一起压缩,会被搞混,一定要在每个自调用函数的前面+;分号表示前面的语句结束了,来避免混在一起。
自调用函数
;(function(window(形参,可以改成其他命名),undefined(形参)){ function abc(){ console.log(123); } window(形参).abc=abc;//这里的window也可以改成其他命名 })(window(实参),undefined(实参)) abc();
//自调用函数传入window的意义是让变量名可以被压缩,传入undefined的意义是,在老版本浏览器中,undefined可以被重新赋值,我们传入undefined 怕它在老版本浏览器中被改变
工作中 多个面向对象 多个js 运行代码写在main.js中,并且main.js要在所有js都引用完后再引用。
例子
;(function (window, undefined) { var Tools = { getRandom: function (min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } } // 暴露Tools给window window.Tools = Tools; })(window, undefined)
函数的第三种创建方式
几乎不用,反应速度慢。但是可以意识到函数也是对象
高阶函数
1.函数作为参数
函数可以作为参数传递
实际案例
return a-b是从小到大 return b-a是从大到小
让用户选择从大到小,还是从小到大排序
2。函数作为返回值的时候
实例
这里的fn()是函数function(){ return random};并不调用getrandom
个人总结重点
对象是类的实例和具体,类是对象的集合 构造函数 var arr=new Array() 对象(arr) 类(Array) 对象是由类构造的
instanceof 检查一个对象是否是一个类的实例 console.log(arr instanceof Array) 另外 所有对象都是Object对象的后代,所有任何对象和Object做instanceof都会返回true
给对象添加属性比如 arr[0]["age']=12; arr[0].age=12
对象浅拷贝 深拷贝 浅拷贝是通过for循环把一个对象复制另一个对象成员,原对象a里的对象改变,拷贝的对象b也会改变 深拷贝 多层复制 要自己建一个copy函数 通过for循环把a对象的每一个key值,循环出来,再通过instanceof 判断是对象还是数组还是属性,是对象的话 b[key]={} 再用函数copy(a[key],b[key]) 具体代码看上面详细
原型(prototype)用来存放公用的方法和属性的 当调用对象的属性或者方法时,先去找对象本身的属性/方法 如果对象没有 去调用原型中的对象和方法
如果属性值是在原型上,而不是在对象上。对象设置属性值的时候,不会搜索到原型链上,而是会给对象新增一个属性
原型链 构造函数 =》prototype=原型对象 =》constructor=构造函数 =》创建对象=》_proto_=原型对象
所有对象都有toString方法
重新设置prototype时,要重新设置constructor属性,另外数组和string的prototype不可修改
自调用函数 直接把对象或者函数赋值给window
;(function(window,undefined)){ function abc(){ ...; } window.abc=abc; })(window,undefined)