再学JavaScript-第四课-面向对象

2024-03-13 23:32

本文主要是介绍再学JavaScript-第四课-面向对象,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、类的创建与实例对象
面向对象的预言有一个标志,那就是他们都有类的概念,通过类可以创建任意多个具有相同属性和方法的对象。但在ECMAScript中没有类的概念,但是我们可以通过其他方式来模拟面向对象的类。
1. 工厂模式:工厂模式是软件工厂领域中一种广为人知的设计模式。
2. 构造函数模式:比如像ECMAScript中的Array、Object、Date等都是通过构造函数来创建的。
3. 动态原型模式
4. 寄生构造函数模式
5. 稳妥构造函数模式

实例代码:

第一种模式:工厂

/*** 工厂模型* @param name* @param sex* @param age* @returns {Object}*/
function  createPerson(name,sex,age) {var obj = new Object();obj.name = name;obj.sex = sex;obj.age = age;return obj;
}//使用
var p1 = createPerson("张三","男",20);
alert(p1.name);

第二种模式:构造函数

/*** 构造函数式  推荐的模式* @param name* @param sex* @param age* @constructor*/
function Person(name,sex,age){this.name = name;this.sex = sex;this.age = age;this.sayName = function () {alert(this.name);}
}
//使用
var pp1 = new Person("王五","男",20);
var pp2 = new Person("呵呵","女",40)
pp1.sayName();

下面简单分析下上面创建的几个对象:

alert(pp1==pp2);      // false  pp1和pp2是不同的对象
alert(pp1.constructor==Person); //true  温故下,每一个对象都有一个属性constructor  
alert(pp2.constructor==Person); //true  pp1和pp2都是用Person的构造器创建的
alert(pp1 instanceof Person);  //true
alert(pp2 instanceof Person);  //true
alert(pp1 instanceof Object);  //true 任何对象都是Object的实例

创建对象的几种方式:

//1.当做构造函数去调用new Person();
//2.作为普通函数去调用Person("老毛","男",30);  // 这样做的效果实际上是 window.Persion(),在全局作用域调用了Person方法,那么全局作用域里就多个 name、sex、age属性和sayName方法。//3.在另一个对象的作用域中调用var objj  = new Object();Person.call(objj,"小吴","男",30);

二、原型prototype

  • 我们创建的每一个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。
  • 原型对象实际就是一个构造函数的实例对象,与普通实例对象没有什么本质的区别,JavaScript中每一个对象都有一个原型对象。不过他比较特殊,该对象所包含的所有属性和方法能够供构造函数的所有实例共享,这就是其他语言所说的继承,而JavaScript通过原型对象来实现继承,简称原型继承。静态原型继承:Object.prototype.[method field];

下面比较下原型方法和实例方法:

function Person (name,age) {this.name = name;this.age = age;this.sayName = function () {alert("我是名字");}
}var p1 = new Person("xx",12);
var p2 = new Person("xx",14);alert(p1.sayName==p2.sayName);  // false
// 简单分析 发现每一个对象里 都会有个sayName方法 这是没有必要的 可以改造下上面那个构造函数
function Person (name,age) {this.name = name;this.age = age;this.sayName = sayName;
}var sayName = function () {alert("我是名字");
}var p1 = new Person("xx",12);
var p2 = new Person("xx",14);alert(p1.sayName==p2.sayName);  // true
//上面那种 方式 还是会有问题的  sayName成了全局的函数了 ,任何 对象都可以调用sayName方法 这样的话 这个方法就超出了Person对象了
//那么 我们可以将公用的方法放到一个对象里 ,这个对象就是prototype对象。
function Person (name,age) {this.name = name;this.age = age;}
Person.prototype.sayName = function () {alert("我是名字");
};var p1 = new Person("xx",12);
var p2 = new Person("xx",14);alert(p1.sayName==p2.sayName);  // true

prototype : 创建每一个函数都有一个prototype属性,这个属性其实是一个指针,而这个指针总是指向一个对象
这个对象的用途就是将特定的属性和方法包含在内,起到一个所有实例所共享的作用。

原型图:
这里写图片描述

注意区分和理解:构造函数、原型对象、实例对象
构造函数.prototype = 原型对象
原型对象.constructor = 构造函数
实例对象.prototype = 原型对象

三、关于原型的几个方法

  • Object.getPrototypeOf();//根据实例对象得到原型对象
  • hasOwnProperty(“”);

一个例子:

function Person () {}
Person.prototype.name = "XX";
Person.prototype.age = 12;
Person.prototype.sayName = function () {alert("这是原型对象方法");
};var p1 = new Person();
alert(p1.name);
var prototypeObj = Object.getPrototypeOf(p1);
alert(prototypeObj == Person.prototype);   // true

一个例子

/*** 是否是原型对象属性* @param obj* @param name*/
function  isPrototypeProperty(obj,name) {return !obj.hasOwnProperty(name)  && name in obj;
}
//注意:in操作符会枚举对象里的所有属性,不区分对象属性和原型属性
  • ECMA5新特性 Object.keys(); 拿出对象的所有属性 返回数组
  • ECMA5新特性constructor属性是不能被枚举的[enable=false]。
    Object.getOwnPropertyNames 枚举对象的所有属性,不管该内部属性是否能被枚举

四、扩展Array,实现each方法
ECMA5里有了forEach()方法,但对多维数组的处理不是很好。下面就来扩展下Array对象,为其添加each方法,并且兼容多维数组。

Array.prototype.each = function (handler) {try{if(this.length>0 && handler.constructor == Function){for(var i =0;i<this.length;i++){var item = this[i];if(item instanceof Array){item.each(handler);}else{//1.直接调用回调
//              handler(e);//2. 用call绑定,底层代码多这样写handler.call(item,item);     // 关于第一个参数  其实他没有对象  可以写 任何对象 null都可以。//3 与call类似 就是传参数的方式有些不同
//                 handler.apply(null,[item]);}}}}catch(ex){//do something}
}//测试代码var arr = [1,2,3,4,5,[3,[4,6]]];arr.each2(function (e) {alert(e);});

五、简单原型的使用

1.直接通过对象字面量来重写整个原型对象(这种方法会改变原型对象的构造器)
先做一个测试:
定义一个类(构造器模板):

function Person(){
}//打印原型对象的构造器
alert(Person.prototype.constructor);

打印的结果是:
这里写图片描述

很明显,Person的原型对象的构造器是Person模板
下面来改造Person的prototype对象:

Person.prototype = {name:"XX",age:10,say: function () {alert("我是原型方法");}
};alert(Person.prototype.constructor);

打印的结果如下:
这里写图片描述
发现,Person 的原型改变了。这不是我们希望的结果。
那么我们可以再改造下,把prototype对象的构造器属性改回来:

Person.prototype = {constructor:Person,   //防止构造器属性改变name:"XX",age:10,say: function () {alert("我是原型方法");}
};

这样目的达到了。
但是,这样还会引起一个问题,我们知道对象的构造器属性是不能够被枚举的,也就是说不能够用for-in语法遍历到对象的constructor属性。经过上面这种方式改造后,constructor属性变成可以遍历的。
ECMA5提供了一个新的方法来修复这个bug:
再改造:

Person.prototype = {name:"XX",age:10,say: function () {alert("我是原型方法");}
};// 参数1:重构构造器的对象  2.数值什么属性  3.options参数
Object.defineProperty(Person.prototype,"constructor",{enumerable:false,value:Person
});alert(Person.prototype.constructor);  //

2.原型对象的动态性(注意原型和创建实例的先后顺序)
先做一个测试:

function Person(){
}
var p1 = new Person();
Person.prototype = {constructor:Person,  say: function () {alert("我是原型方法");}
};
p1.say();  // 报错了:undefined is not a function

为什么呢:
因为在重写prototype对象时,他与Person的关系被切断了,而new p1对象时,say还么有定义,所以p1对象是没有say方法的。
正确的做法那是:

function Person(){
}
Person.prototype = {constructor:Person,   say: function () {alert("我是原型方法");}
};
var p1 = new Person();
p1.say();  // OK ,可以正常调用

注意:简单原型使用的顺序,实例对象必须在原型对象之后创建。

六、原型对象的常用开发模式
1.原型对象虽然可以对所有实例共享属性和方法,但是它的局限性也很明显,正式因为共享的特性,也导致原型存在的最大问题。

function Person(){}Person.prototype = {constructor:Person,   //防止构造器属性改变name:"王五",friends:['小A','小B','小C'],say: function () {alert("我是原型方法");}
};
var p1 = new Person();
var p2 = new Person();
p1.friends.push("小D");
alert(p1.friends);
alert(p2.friends);// 发现这两个对象打印了同样的内容

2.我们一般组合使用构造函数式和原型模式,在实际开发中,这种模式也是应用最为广泛的。

function Person(name,friends){this.name = name;this.friends = friends;
}
Person.prototype = {constructor:Person,   say: function () {alert(this.name);}
};

3.动态原型模式:就是把信息都封装到函数中,这样体现了封装的概念。

function Person(name,friends){this.name = name;this.friends = friends;if(typeof this.sayName != "function"){Person.prototype.sayName = function () {alert(this.name);}}
}

4.稳妥构造函数式:所谓稳妥模式就是没有公共属性,而且其他方法也不引用this对象,稳妥模式最适合在安全环境中使用。若果你的程序对于安全性要求高,那么非常适合这种模式。

function Person(name){//创建一个要返回的对象var obj = new Object();//可以定义一下私有的变量和函数var name = name;obj.sayName = function () {alert(name);};return obj;
}

七、深入解析原型继承的概念

  1. 知道了构造函数、原型对象的关系,如果我们让原型对象等于另外一个类型的实例,结果就是原型对象将包含一个指向另一个原型的指针,相应的另一个原型中也包含这一个指向另一个构造函数的指针。
  2. 原型链:利用原型让一个引用类型继承另外一个引用类型的属性和方法。
  3. 简单继承(原型继承)
  4. 类继承(模板继承或借用构造函数继承)
  5. 混合使用继承实现完整的继承。

原型继承

    var Sup = function (name) {this.name = name;}var Sub = function (age) {this.age = age;}Sub.prototype = new Sup();var sub1 = new Sub();alert(sub1.constructor);  //构造函数式Sup的构造函数模板

类继承,贴切的叫法是:借用构造函数继承

   var Person = function (name, age) {this.name = name;this.age = age;};Person.prototype.sayName = function () {alert(this.name);};var Student = function (name,age,sex) {Person.call(this,name,age);this.sex = sex;}
//这样子类拥有了父类的属性,但却没能继承父类的原型方法,子类的构造器依然是Student的模板

实用的做法是:混合继承:原型继承+借用构造函数

 var Person = function (name, age) {this.name = name;this.age = age;};Person.prototype.sayName = function () {alert(this.name);};var Student = function (name,age,sex) {Person.call(this,name,age);this.sex = sex;}Student.prototype = new Person();alert(stu.name);alert(stu.age);alert(stu.sex);alert(stu.constructor); //构造函数式父类的构造器模板  flag1stu.sayName();

这是flag1弹出的结果:
这里写图片描述

这篇关于再学JavaScript-第四课-面向对象的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/806554

相关文章

Java反射实现多属性去重与分组功能

《Java反射实现多属性去重与分组功能》在Java开发中,​​List是一种非常常用的数据结构,通常我们会遇到这样的问题:如何处理​​List​​​中的相同字段?无论是去重还是分组,合理的操作可以提高... 目录一、开发环境与基础组件准备1.环境配置:2. 代码结构说明:二、基础反射工具:BeanUtils

在Java中将XLS转换为XLSX的实现方案

《在Java中将XLS转换为XLSX的实现方案》在本文中,我们将探讨传统ExcelXLS格式与现代XLSX格式的结构差异,并为Java开发者提供转换方案,通过了解底层原理、性能优势及实用工具,您将掌握... 目录为什么升级XLS到XLSX值得投入?实际转换过程解析推荐技术方案对比Apache POI实现编程

Java调用C#动态库的三种方法详解

《Java调用C#动态库的三种方法详解》在这个多语言编程的时代,Java和C#就像两位才华横溢的舞者,各自在不同的舞台上展现着独特的魅力,然而,当它们携手合作时,又会碰撞出怎样绚丽的火花呢?今天,我们... 目录方法1:C++/CLI搭建桥梁——Java ↔ C# 的“翻译官”步骤1:创建C#类库(.NET

Java中JSON格式反序列化为Map且保证存取顺序一致的问题

《Java中JSON格式反序列化为Map且保证存取顺序一致的问题》:本文主要介绍Java中JSON格式反序列化为Map且保证存取顺序一致的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未... 目录背景问题解决方法总结背景做项目涉及两个微服务之间传数据时,需要提供方将Map类型的数据序列化为co

Java Lambda表达式的使用详解

《JavaLambda表达式的使用详解》:本文主要介绍JavaLambda表达式的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、前言二、Lambda表达式概述1. 什么是Lambda表达式?三、Lambda表达式的语法规则1. 无参数的Lambda表

java中Optional的核心用法和最佳实践

《java中Optional的核心用法和最佳实践》Java8中Optional用于处理可能为null的值,减少空指针异常,:本文主要介绍java中Optional核心用法和最佳实践的相关资料,文中... 目录前言1. 创建 Optional 对象1.1 常规创建方式2. 访问 Optional 中的值2.1

Spring Boot 整合 Apache Flink 的详细过程

《SpringBoot整合ApacheFlink的详细过程》ApacheFlink是一个高性能的分布式流处理框架,而SpringBoot提供了快速构建企业级应用的能力,下面给大家介绍Spri... 目录Spring Boot 整合 Apache Flink 教程一、背景与目标二、环境准备三、创建项目 & 添

Spring组件实例化扩展点之InstantiationAwareBeanPostProcessor使用场景解析

《Spring组件实例化扩展点之InstantiationAwareBeanPostProcessor使用场景解析》InstantiationAwareBeanPostProcessor是Spring... 目录一、什么是InstantiationAwareBeanPostProcessor?二、核心方法解

深入解析 Java Future 类及代码示例

《深入解析JavaFuture类及代码示例》JavaFuture是java.util.concurrent包中用于表示异步计算结果的核心接口,下面给大家介绍JavaFuture类及实例代码,感兴... 目录一、Future 类概述二、核心工作机制代码示例执行流程2. 状态机模型3. 核心方法解析行为总结:三

Spring @RequestMapping 注解及使用技巧详解

《Spring@RequestMapping注解及使用技巧详解》@RequestMapping是SpringMVC中定义请求映射规则的核心注解,用于将HTTP请求映射到Controller处理方法... 目录一、核心作用二、关键参数说明三、快捷组合注解四、动态路径参数(@PathVariable)五、匹配请