网站前台做哪些工作内容,交互设计软件,免费金融网站模板,建设网站用什么语言好学习目标理解面向对象开发思想掌握 JavaScript 面向对象开发相关模式面向对象介绍什么是对象Everything is object (一切皆对象)我们可以从两个层次来理解对象#xff1a;(1) 对象是单个事物的抽象。一本书、一辆汽车、一个人都可以是对象#xff0c;一个数据库、一张网页、一… 学习目标理解面向对象开发思想掌握 JavaScript 面向对象开发相关模式面向对象介绍什么是对象Everything is object (一切皆对象)我们可以从两个层次来理解对象(1) 对象是单个事物的抽象。 一本书、一辆汽车、一个人都可以是对象一个数据库、一张网页、一个与远程服务器的连接也可以是对象。当实物被抽象成对象实物之间的关系就变成了对象之间的关系从而就可以模拟现实情况针对对象进行编程。(2) 对象是一个容器封装了属性(property)和方法(method)。 属性是对象的状态方法是对象的行为(完成某种任务)。比如我们可以把动物抽象为animal对象使用“属性”记录具体是那一种动物使用“方法”表示动物的某种行为(奔跑、捕猎、休息等等)。 在实际开发中对象是一个抽象的概念可以将其简单理解为数据集或功能集。ECMAScript-262 把对象定义为无序属性的集合其属性可以包含基本值、对象或者函数。严格来讲这就相当于说对象是一组没有特定顺序的值。对象的每个属性或方法都有一个名字而每个名字都 映射到一个值。提示每个对象都是基于一个引用类型创建的这些类型可以是系统内置的原生类型也可以是开发人员自定义的类型。什么是面向对象面向对象不是新的东西它只是过程式代码的一种高度封装目的在于提高代码的开发效率和可维护性。 面向对象编程 —— Object Oriented Programming简称 OOP 是一种编程开发思想。它将真实世界各种复杂的关系抽象为一个个对象然后由对象之间的分工与合作完成对真实世界的模拟。 在面向对象程序开发思想中每一个对象都是功能中心具有明确分工可以完成接受信息、处理数据、发出信息等任务。因此面向对象编程具有灵活、代码可复用、高度模块化等特点容易维护和开发比起由一系列函数或指令组成的传统的过程式编程(procedural programming)更适合多人合作的大型软件项目。面向对象与面向过程区别面向过程就是亲力亲为事无巨细面面俱到步步紧跟有条不紊。面向对象就是找一个对象指挥得结果。面向对象将执行者转变成指挥者。面向对象不是面向过程的替代而是面向过程的封装。面向对象的特性封装性继承性多态性JavaScript 中面向对象的基本体现 在 JavaScript 中所有数据类型都可以视为对象当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类( Class )的概念。 我们以一个例子来说明面向过程和面向对象在程序流程上的不同之处。 假设我们要处理学生的成绩表为了表示一个学生的成绩面向过程的程序可以用一个对象表示var std1 { name: 张三, score: 98 }var std2 { name: 李四, score: 81 } 而处理学生成绩可以通过函数实现比如打印学生的成绩function printScore (student) { console.log(姓名 student.name 成绩 student.score)} 如果采用面向对象的程序设计思想我们首选思考的不是程序的执行流程 而是 Student 这种数据类型应该被视为一个对象这个对象拥有 name 和 score 这两个属性(Property)。如果要打印一个学生的成绩首先必须创建出这个学生对应的对象然后给对象发一个 printScore 消息让对象自己把自己的数据打印出来。抽象数据行为模板(Class)function Student (name, score) { this.name name this.score score}Student.prototype.printScore function () { console.log(姓名 this.name 成绩 this.score)}根据模板创建具体实例对象(Instance)var std1 new Student(张三, 98)var std2 new Student(李四, 81)实例对象具有自己的具体行为(给对象发消息)std1.printScore() // 姓名张三 成绩98std2.printScore() // 姓名李四 成绩 81 面向对象的设计思想是从自然界中来的因为在自然界中类(Class)和实例(Instance)的概念是很自然的。Class 是一种抽象概念比如我们定义的 Class——Student 是指学生这个概念 而实例(Instance)则是一个个具体的 Student 比如 张三 和 李四 是两个具体的 Student 。面向对象的设计思想是抽象出 Class根据 Class 创建 Instance指挥 Instance 得结果 面向对象的抽象程度比函数要高因为一个 Class 既包含数据又包含操作数据的方法。JavaScript 如何创建对象字面量方式 我们可以直接通过 new Object() 创建var person new Object()person.name 张三person.age 18person.sayName function () { console.log(this.name)} 每次创建通过 new Object() 比较麻烦所以可以通过它的简写形式对象字面量来创建var person { name: 张三, age: 18, sayName: function () { console.log(this.name) }} 上面的写法是没有问题的但是假如我们要生成两个 person 实例对象呢var person1 { name: 张三, age: 18, sayName: function () { console.log(this.name) }}var person2 { name: 李四, age: 16, sayName: function () { console.log(this.name) }} 通过上面的代码我们不难看出这样写的代码太过冗余重复性太高。简单方式的改进工厂函数 我们可以写一个函数解决上边代码重复的问题function createPerson (name, age) { return { name: name, age: age, sayName: function () { console.log(this.name) } }} 生成实例对象var p1 createPerson(张三, 18)var p2 createPerson(李四, 18) 这样封装比上边的方式好多了通过工厂模式我们解决了创建多个相似对象代码冗余的问题 但却没有解决对象识别的问题(即怎样知道一个对象的类型)。更优雅的工厂函数构造函数 一种更优雅的工厂函数就是下面这样构造函数function Person (name, age) { this.name name this.age age this.sayName function () { console.log(this.name) }}var p1 new Person(张三, 18)p1.sayName() // 张三var p2 new Person(李四, 23)p2.sayName() // 李四 在上面的示例中Person() 函数取代了 createPerson() 函数但是实现效果是一样的。这是为什么呢 我们注意到Person() 中的代码与 createPerson() 有以下几点不同之处没有显示的创建对象直接将属性和方法赋给了 this 对象没有 return 语句函数名使用的是大写的 Person构造函数代码执行过程 要创建 Person 实例则必须使用 new 操作符。以这种方式调用构造函数会经历以下 4 个步骤创建一个新对象。将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象)。执行构造函数中的代码。返回新对象。 下面是具体的伪代码function Person (name, age) { // 当使用 new 操作符调用 Person() 的时候实际上这里会先创建一个对象 // var instance {} // 然后让内部的 this 指向 instance 对象 // this instance // 接下来所有针对 this 的操作实际上操作的就是 instance this.name name this.age age this.sayName function () { console.log(this.name) } // 在函数的结尾处会将 this 返回也就是 instance // return this}构造函数和实例对象的关系 使用构造函数的好处不仅仅在于代码的简洁性更重要的是我们可以识别对象的具体类型了。在每一个实例对象中的_proto_中同时有一个 constructor 属性该属性指向创建该实例的构造函数console.log(p1.constructor Person) // trueconsole.log(p2.constructor Person) // trueconsole.log(p1.constructor p2.constructor) // true 对象的 constructor 属性最初是用来标识对象类型的 但是如果要检测对象的类型还是使用 instanceof 操作符更可靠一些console.log(p1 instanceof Person) // trueconsole.log(p2 instanceof Person) // true总结构造函数是根据具体的事物抽象出来的抽象模板。实例对象是根据抽象的构造函数模板得到的具体实例对象。每一个实例对象都具有一个 constructor 属性指向创建该实例的构造函数。( 此处constructor 是实例的属性的说法不严谨具体后面的原型会讲到)可以通过实例的 constructor 属性判断实例和构造函数之间的关系。(这种方式不严谨推荐使用 instanceof 操作符后面学原型会解释为什么)构造函数的问题 使用构造函数带来的最大的好处就是创建对象更方便了但是其本身也存在一个浪费内存的问题function Person (name, age) { this.name name this.age age this.type 学生 this.sayHello function () { console.log(hello this.name) }}var p1 new Person(王五, 18)var p2 new Person(李四, 16) 上边的代码从表面看上好像没什么问题但是实际上这样做有一个很大的弊端。那就是对于每一个实例对象type和 sayHello 都是一模一样的内容 每一次生成一个实例都必须为重复的内容多占用一些内存如果实例对象很多会造成极大的内存浪费。console.log(p1.sayHello p2.sayHello) // false 对于这种问题我们可以把需要共享的函数定义到构造函数外部function sayHello function () { console.log(hello this.name)}function Person (name, age) { this.name name this.age age this.type 学生 this.sayHello sayHello}var p1 new Person(王五, 18)var p2 new Person(李四, 16)console.log(p1.sayHello p2.sayHello) // true 这样确实可以了但是如果有多个需要共享的函数的话就会造成全局命名空间冲突的问题。如何解决这个问题呢你肯定想到了可以把多个函数放到一个对象中用来避免全局命名空间冲突的问题var fns { sayHello: function () { console.log(hello this.name) }, sayAge: function () { console.log(this.age) }}function Person (name, age) { this.name name this.age age this.type 学生 this.sayHello fns.sayHello this.sayAge fns.sayAge}var p1 new Person(王五, 18)var p2 new Person(李四, 16)console.log(p1.sayHello p2.sayHello) // trueconsole.log(p1.sayAge p2.sayAge) // true 至此我们利用自己的方式基本上解决了构造函数的内存浪费问题。但是代码看起来还是那么的格格不入那有没有更好的方式呢原型更好的解决方案prototype Javascript 规定每一个构造函数都有一个 prototype 属性指向另一个对象。这个对象的所有属性和方法都会被构造函数的实例继承。 这也就意味着我们可以把所有对象实例需要共享的属性和方法直接定义在 prototype 对象上。function Person (name, age) { this.name name this.age age}console.log(Person.prototype)Person.prototype.type 学生Person.prototype.sayName function () { console.log(this.name)}var p1 new Person(...)var p2 new Person(...)console.log(p1.sayName p2.sayName) // true 这时所有实例的 type 属性和 sayName() 方法 其实都是同一个内存地址指向 prototype 对象因此就提高了运行效率。 构造函数、实例、原型三者之间的关系 任何函数都有一个 prototype 属性该属性是一个对象。function F () {}console.log(F.prototype) // objectF.prototype.sayHi function () { console.log(hi!)} 构造函数的 prototype 对象默认都有一个 constructor 属性指向 prototype 对象所在函数。console.log(F.constructor F) // true 通过构造函数得到的实例对象内部会包含一个指向构造函数的 prototype 对象的指针 __proto__。var instance new F()console.log(instance.__proto__ F.prototype) // true__proto__ 是非标准属性。实例对象可以直接访问原型对象成员instance.sayHi() // hi!总结任何函数都具有一个 prototype 属性该属性是一个对象。构造函数的 prototype 对象默认都有一个 constructor 属性指向 prototype 对象所在函数。通过构造函数得到的实例对象内部会包含一个指向构造函数的 prototype 对象的指针 __proto__。所有实例都直接或间接继承了原型对象的成员。属性成员的搜索原则原型链 了解了 构造函数-实例-原型对象 三者之间的关系后接下来我们来解释一下为什么实例对象可以访问原型对象中的成员 每当代码读取某个对象的某个属性时都会执行一次搜索目标是具有给定名字的属性。搜索首先从对象实例本身开始。如果在实例中找到了具有给定名字的属性则返回该属性的值。如果没有找到则继续搜索指针指向的原型对象在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这个属性则返回该属性的值。也就是说在我们调用 person1.sayName() 的时候会先后执行两次搜索首先解析器会问“实例 person1 有 sayName 属性吗”答“没有。然后它继续搜索再问“ person1 的原型有 sayName 属性吗”答“有。于是它就读取那个保存在原型对象中的函数。当我们调用 person2.sayName() 时将会重现相同的搜索过程得到相同的结果。 这就是多个对象实例共享原型所保存的属性和方法的基本原理。总结先在自己身上找找到即返回。自己身上找不到则沿着原型链向上查找找到即返回。如果一直到原型链的末端还没有找到则返回 undefined。实例对象读写原型对象成员读取先在自己身上找找到即返回。自己身上找不到则沿着原型链向上查找找到即返回。如果一直到原型链的末端还没有找到则返回 undefined。值类型成员写入(实例对象.值类型成员 xx)当实例期望重写原型对象中的某个普通数据成员时实际上会把该成员添加到自己身上。也就是说该行为实际上会屏蔽掉对原型对象成员的访问。引用类型成员写入(实例对象.引用类型成员 xx)同上。复杂类型修改(实例对象.成员.xx xx)同样会先在自己身上找该成员如果自己身上找到则直接修改。如果自己身上找不到则沿着原型链继续查找如果找到则修改。如果一直到原型链的末端还没有找到该成员则报错(实例对象.undefined.xx xx)。更简单的原型语法 我们注意到前面例子中每添加一个属性和方法就要敲一遍 Person.prototype 。 为减少不必要的输入更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象function Person (name, age) { this.name name this.age age}Person.prototype { type: 学生, sayHello: function () { console.log(我叫 this.name 我今年 this.age 岁了) }} 在该示例中我们将 Person.prototype 重置到了一个新的对象。这样做的好处就是为 Person.prototype 添加成员简单了但是也会带来一个问题那就是原型对象丢失了 constructor 成员。 所以我们为了保持 constructor 的指向正确建议的写法是function Person (name, age) { this.name name this.age age}Person.prototype { constructor: Person, // 手动将 constructor 指向正确的构造函数 type: 学生, sayHello: function () { console.log(我叫 this.name 我今年 this.age 岁了) }}原生对象的原型所有函数都有 prototype 属性对象。Object.prototypeFunction.prototypeArray.prototypeString.prototypeNumber.prototypeDate.prototype... 为数组对象和字符串对象扩展原型方法//为内置对象添加原型方法//我们在系统的对象的原型中添加方法,相当于在改变源码//我希望字符串中有一个倒序字符串的方法String.prototype.myReverse function() {for (var i this.length - 1; i 0; i--) {console.log(this[i]);}};var str abcdefg;str.myReverse();//为Array内置对象的原型对象中添加方法Array.prototype.mySort function() {for (var i 0; i this.length - 1; i) {for (var j 0; j this.length - 1 - i; j) {if (this[j] this[j 1]) {var temp this[j];this[j] this[j 1];this[j 1] temp;} //end if} // end for} //end for};var arr [100, 3, 56, 78, 23, 10];arr.mySort();console.log(arr);String.prototype.sayHi function() {console.log(this 哈哈,我又变帅了);};//字符串就有了打招呼的方法var str2 小杨;str2.sayHi();原型对象的一些问题共享数组共享对象 如果真的希望可以被实例对象之间共享和修改这些共享数据那就不是问题。但是如果不希望实例之间共享和修改这些共享数据则会出现问题。一个更好的建议是最好不要让实例之间互相共享数组或者对象成员一旦修改的话会导致数据的走向很不明确而且难以维护。原型对象使用建议私有成员(一般就是非函数成员)放到构造函数中。共享成员(一般就是函数)放到原型对象中。如果重置了 prototype 记得修正 constructor 的指向。