一、首先得分清面向过程与面向对象。
面向过程注重的是具体的步骤,只有按照步骤一步一步执行,才能够完成这件事情。
面向对象思想注重的是一个个对象,这些对象各司其职,我们只要发号施令,即可指挥这些对象帮我们完成任务。
面向对象的特征
面向对象的特征主要可以概括为封装性、继承性和多态性。
1. 封装性
封装指的是隐藏内部的实现细节,只对外开放操作接口。接口就是对象的方法,无论对象的内部多么复杂,用户只需知道这些接口怎么使用即可。
封装的优势在于,无论一个对象内部的代码经过了多少次修改,只要不改变接口,就不会影响到使用这个对象时编写的代码。
2. 继承性
继承是指个对象继承另一个对象的成员, 从而在不改变另一 个对象的前提下进行扩展。例如狗和猫都属于动物,程序中便可以描速输和构继承自动物。同理,波斯猫和巴重猫都继承自猫科,沙皮狗和斑点狗都继承自犬科。
从波斯猫到猫科,再到动物,是一个逐渐抽象的过程。通过抽象可以使对象的层次结构清晰。例如,当指挥所有的猫捉老鼠时,波斯猫和巴厘猫会听从命令,而犬科动物不受影响。
在JavaScript中,String 对象就是对所有字符串的
抽象,所有字符串都具有toUpperCase()方法,用来将字符串转换为大写,这个方法其实就是继承自String对象。
由此可见,利用继承-方面可以在保持接口兼容的前提下对功能进行扩展,另一方面增强了代码的复用性,为程序的修改和补充提供便利。
3. 多态性
多态指的是同一个操作作用于不同的对象,会产生不同的执行结果。实际上JavaScript被设计成一种弱类型语言(即一个变量可以存储任意类型的数据),就是多态性的体现。例如,数字、数组、函数都有toString()方法,当使用不同的对象调用该方法时,执行结果不同,示例代码如下:
var obj=123;
console.log(obj.toString());//输出结果:123
obj=[1,2,3];
console.log(obj.toString());//输出结果:1,2,3
obj=funcation(){};
console.log(obj.toString());//输出结果:funcation(){}
二、对象的定义
1、对象的定义
在JavaScript中,对象的定义是通过“{}”语法实现的,对象的成员以键值对的形式存放在{}中,多个成员之间使用逗号分隔。
var o1={}; //定义空对象
var o2={name:'Jim'}; //定义含有name属性的对象
var o3={name:'Jim',age:19,gender:'男'}; //定义含有3个属性的对象
当对象的成员比较多时,为了让的代码阅读更加流畅,可以对代码格式进行缩进与换行。
var o4={
name:'Jim', //成员属性o4.name
age:19, //成员属性o4.age
gender:'男', //成员属性o4.gender
sayHello:function(){ //成员属性o4.sayHello()
console.log('你好');
}
}
2、访问对象成员
在创建对象后,通过“,”可以访问对象的成员。JavaScript中的对象具有动态特征,如果一个对象没有成员,用户可以手动赋值属性或方法来添加成员。
例如
<body>
<input id="k" type="text" value="name">
<input id="v" type="text" value="Jack">
<input id="btn" type="text" value="test">
<script>
var k=document.getElementById('k');
var v=document.getElementById('v');
var btn=document.getElementById('btn');
var o={};
btn.onclick=function () {
o[k.value]=v.value;
console.log(o);//输出结果:Object{name:"Jack"}
}
</script>
</body>
通过浏览器访问测试,当单机网页中的按钮后,就会为对象O添加一个属性,该属性的名称是第一个文本框的值“name”,属性的值是第二个文本框的值“Jack”。
;
3、对象成员遍历
使用for…in语法 不仅可以遍历数组元素,还可以遍历对象的成员,具体示例如下:
var obj={name:'Tom',age:16};
for (var k in obj){
console.log(k+'-'+obj[k]);
} //输出结果:name-Tom、age-16
4、深拷贝与浅拷贝
拷贝(copy)是指将一个目标数据复制一份,形成两个个体。在前面的开发中,若将一个基本数据类型(数值、字符型)的变量赋值给另一个变量,就可以得到两个值相同的变量,改变其中一个变量的值,不会影响另一个变量的值。
var p1={name:'Jim',age:19};
var p2=p1;
p2.name='Tom';
console.log(p1); //输出结果:Object{name:"Tom",age:19}
console.log(p2); //输出结果:Object{name:"Tom",age:19}
console,log(p1===p2); //输出结果:true
三、构造函数继承
这里的例子来源是JavaScript高级程序设计
在说构造函数继承之前,我们先看一个例子
var a = {
name: 'a',
};
var name = 'window';
var getName = function(){
console.log(this.name);
}
执行getName()时,函数体的this指向window,而执行getName.call(a)时,函数体的this指向的是a对象,所以就可以理解啦。接下来我们看如何实现构造函数继承
function SuperType () {
this.colors = ['red', 'green'];
}
function SubType () {
// 继承SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push('blue');
console.log(instance1.colors);
// red, green, blue
var instance2 = new SubType();
console.log(instance2.colors);
// red, green
SuperType.call(this); 这一行代码,实际上意思是在SubType的实例初始化过程中,调用了SuperType的构造函数,因此SubType的每个实例都有colors这个属性
优点:子对象可以传递参数给父对象。
function SuperType(name) {
this.name = name;
}
function SubType(name, age) {
name = name || 'hello';
SuperType.call(this, name);
this.age = age;
}
var instance1 = new SubType('scofield', 28);
console.log(instance1.name);
console.log(instance1.age);
需要注意的地方是在调用父对象的构造函数之后,再给子类型中的定义属性,否则会被重写。
缺点:方法都需要在构造函数中定义,难以做到函数的复用,而且在父对象的原型上定义的方法,对于子类型是不可见的。 ??? 为什么不可见
function SuperType(name) {
this.name = name;
}
SuperType.prototype.getName = function() {
return this.name;
}
SuperType.prototype.prefix = function() {
return 'prefix';
}
function SubType(name) {
SuperType.call(this, name);
}
var instance1 = new SubType('scofield');
console.log(instance1.name);
console.log(instance1.prefix);
console.log(instance1.getName());
// Uncaught TypeError: instance1.getName is not a function
- 私有属性和私有方法
私有方法ES6并不提供,但是可以变通
- 命名区分
- 把方法移出模块
- 利用Symbol来命名方法名
const getAge = Symbol('getAge');
export defalut class Person {
// 公有方法
getName(name) {
return name;
},
// 私有方法
[getAge](age) {
return age;
}
}
2、this的指向
类的内部this的指向默认是指向this的实例的,如果单独使用类中的一些包含this的方法,很有可能会报错
分析this指向
在JavaScript中,函数内的this指向通常与以下3种情况有关。
1)、使用new关键字将函数作为构造函数调用时,构造函数内部的this指向新创建的对象。
2)、直接通过函数名调用函数时,this指向的是全局对象
3)、如果将函数作为对象的方法调用,this将会指向该对象。
function foo() {
return this;
}
var o={name:'Jim',func:foo};
console.log(foo()===window);
console.log(o.func()===o);
更改this指向
除了遵循默认的this指向规则,函数的调用者还可以利用JavaScript提供的两种方法手动控制this的指向。一种是通过apply()方法,另一种是通过call()方法。
function method() {
console.log(this.name);
}
method.apply({name:'张三'});
method.call({name: '李四'});
以表单生成器为例
表单数据存储格式分析
- 利用对象保存表格数据。
- 将表单项看成一个个对象,
- 归纳表单项的结构模版,包括tag(标签名)、text(提示文本)、attr(标签属性)、option(选项)四个属性。
代码思路
- 按照表单数据保存的分析创建对象保存相关数据。
- 编写FormBuilder对象,接收表单数据,并将结果显示到指定位置上。
- 编写builder对象,根据传入的数据生成表单项。
- 编写item对象,根据标签名称生成表单项。
以下是js具体代码:
(function (window) {
var FormBuilder=function (data) {
this.data=data;
};
window.FormBuilder=FormBuilder;
})(window);
FormBuilder.prototype.create=function () {
var html='';
for (var k in this.data){
var item={tag:'',text:'',attr:{},option:null};
for (var n in this.data[k]){
item[n]=this.data[k][n];
}
html+=builder.toHTML(item);
}
return '<table>'+html+'</table>';
};
var builder={
toHTML: function (obj) {},
attr: function (attr) {},
item:{
input:function (attr,option) {},
select:function (attr,option) {},
textarea:function (attr) {}
},
toHTML: function(obj) {
var html=this.item[obj.tag](this.attr(obj.attr),obj.option);
return '<tr><th>'+obj.text+'</th><td>'+html+'</td></tr>';
},
attr:function(attr){
var html='';
for (var k in attr){
html+=k+'= "'+attr[k]+'"';
}
return html;
},
input:function(attr,option){
var html='';
if (option===null){
html+='<input'+attr+'>';
} else {
for (var k in option){
html+='<label><input'+attr+'value="'+k+'"'+'>';
html+=option[k]+'</lable>';
}
}
return html;
},
select:function(attr,option){
var html='';
for (var k in option){
html+='<option value="'+k+'">'+option[k]+'</option>';
}
return '<select'+attr+'>'+html+'</select>';
},
textarea:function(attr){
return '<textarea'+attr+'></textarea>';
}
};
上述代码用到了封装、匿名函数等
考虑到表单生成器是一个独立的功能,我们可以将它封装成一个构造函数,从而使代码更好的复用。
上述代码最外层是一个自调用的匿名函数,在调用时传入的window对象用于控制FormBuilder库的作用范围,通过 window.FormBuilder=FormBuilder 将FormBuilder作为传入对象的属性。由于window对象是全局的,因此当上述代码执行后,就可以直接使用FormBuilder。另一方面,在匿名函数中定义的变量、函数,都不会污染全局作用域,体现了面向对象的封装性。