浅谈JavaScript面向对象特性的理解

本文介绍了面向对象编程的基本概念,包括封装、继承和多态性等核心特性,并通过JavaScript语言进行了详细解析。此外还展示了如何使用构造函数实现继承,以及如何通过自调用匿名函数封装表单生成器。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、首先得分清面向过程与面向对象。

     面向过程注重的是具体的步骤,只有按照步骤一步一步执行,才能够完成这件事情。
面向对象思想注重的是一个个对象,这些对象各司其职,我们只要发号施令,即可指挥这些对象帮我们完成任务。

面向对象的特征
面向对象的特征主要可以概括为封装性、继承性和多态性。

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

  1. 私有属性和私有方法

私有方法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: '李四'});

以表单生成器为例

表单数据存储格式分析

  1. 利用对象保存表格数据。
  2. 将表单项看成一个个对象,
  3. 归纳表单项的结构模版,包括tag(标签名)、text(提示文本)、attr(标签属性)、option(选项)四个属性。

代码思路

  1. 按照表单数据保存的分析创建对象保存相关数据。
  2. 编写FormBuilder对象,接收表单数据,并将结果显示到指定位置上。
  3. 编写builder对象,根据传入的数据生成表单项。
  4. 编写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。另一方面,在匿名函数中定义的变量、函数,都不会污染全局作用域,体现了面向对象的封装性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值