@GivenCui
        
        2016-05-28T06:13:54.000000Z
        字数 14819
        阅读 930
    js高级
// 用字面量语法创建了一个对象// 注意: {}var obj = {// 属性name : "小明",age : 18};
- 思想:解决某个问题,看的是"如何"解决这个问题,是一种数学逻辑的映射,按照步骤执行.
 - 定义:
 
- 思想: 解决某个问题,看的是"谁"来解决这个问题,把问题拆解成各个对象处理相应的功能逻辑(回到了面向过程),"对象"之间协同完成工作,是一种生活逻辑的映射.
 

唯独JS, JS没有类的对象(最新版有类的概念但是与其他程序不一样);
[类] : 是一类事物的抽象,是创建对象的模板.
[对象] : 是某个类抽象的一个实例. (真实的产物)
| 例子 | 类 | 对象 | 
|---|---|---|
| 人 | 亚洲人 | 奥巴马 | 
| 车 | 跑车 | 布加迪威龙限量版 | 
| hero | 葫芦娃 | 美国队长,钢铁侠,绿巨人 | 
封装 :例如函数,引用.js的文件等(例如c语言的结构体)
函数,从封装的角度讲,是对功能逻辑的一种封装,在面向对象中叫做方法
类,是对属性(特征)和方法(行为)的一种封装.
[总结] : js中没有"类"的概念,但是我们做的是面向对象编程,我们最更本的是要利用"对象"去编程,"对象"是编程的基本单位.js中虽然没有"类",但我们可以模拟"类"的概念.有"对象"的前提一定得先有"类"
C语言中程序存储时分三类:
分为 代码区(text)、数据区(data)和未初始化数据区(bss) 3个部分
程序运行时候分五类 :
分为 代码区、初始化数据区、未初始化数据区、堆区和栈区 5个部分
1.栈内存
- var变量实际上是个指针,是通过指针指向某个对象.
 - 栈中存储的是指针变量.
 
2.堆内存
- 通过new关键字创建的对象都会存储在堆内存中.
 - 堆内存中存放的是数据块.
 

相关知识链接 : C语言中内存分配
<!DOCTYPE html><html><head><meta charset="utf-8" /></head><body><script type="text/javascript">//通过构造函数创建对象/*注意:构造函数和普通的函数一样,但为了区分,我们往往把构造函数的首字母大写。*///声明“类”,用来创建对象//一般构造函数的参数就是属性需要什么,我们传什么function Person (name, age) {//声明属性//this代表当前对象实例// this.name = "林心如";// this.age = 40;//这样不会把名字和年龄写死,通过构造函数的参数变活了。this.name = name;this.age = age;//声明一个私有属性//私有属性就是外部函数无法直接访问到var _height = 172;//模拟JS中set 和 get访问器 来访问私有属性//设值方法 (访问私有变量)this.setHeight = function (height) {_height = height;}//取值方法 (访问私有变量)this.getHeight = function () {return _height;}//声明方法this.sayHello = function () {console.log("大家好,我是:" + this.name + ",我的年龄是:" + this.age);}}//给Person添加一个静态方法(类方法),静态方法是直接通过构造函数的名字调用的,不关心有没有对象实例的存在。//例如:Math.random();Person.staticFn = function () {console.log("这仅仅是一个直接用构造函数名字调用的静态方法.");}//一般我们可以用静态方法去创建对象,这样可以简化代码,实质上还是用new + 构造函数创建对象Person.createPerson = function (name, age) {var tempPerson = new Person(name, age);return tempPerson;}//通过Person构造函数创建一个对象实例//注意:通过new 和 构造函数 去创建对象/*来看下new Person() 在内存中都做了什么?通过new关键字创建的对象 都会存储在 堆内存中。*//*只要看到new + 构造函数,就代表在堆内存中开辟了一块空间(对象),同时会生成一个地址。我们想访问这块空间(对象),只能通过变量指针(person1)来访问,有的时候我们会说person1就是对象,其实是不对的,person1是指向了那个对象的一个指针而已。*/var person1 = new Person("林心如" , 40);//用对象调用方法person1.sayHello();// 这里没有生成新的对象,而是person2指针指向了和person1指针相同的内存地址。也就是搜,person1 和person2 都可以操作这个内存地址生成的对象。var person2 = person1;//修改person2的名字 和 年龄person2.name = "范冰冰";person2.age = 36;//调用person2的sayHello方法person2.sayHello();//这里调用person1的sayHello方法person1.sayHello(); //经验证,person1 和person2指向的是一个对象,因为修改了person2的name值,person1的值也跟着改变了。//还可以直接判断两个指针是否指向同一块内存空间console.log(person1 === person2);//再创建一个新的对象var person3 = new Person("随便叫", 50);person3.name = "尔康";person3.sayHello();person1.name = "孟丹";person2.sayHello();person1.sayHello();//instanceof 运算符用来判断 一个变量指针是否是通过某个构造函数创建出来的。//Object 是万物的根本,是所有对象的根(基)对象console.log(person1 instanceof Person); //trueconsole.log(person1 instanceof Object); //trueconsole.log(person1 instanceof String); //false//typeof 判断类型console.log(typeof person1);console.log(typeof "");//测试能否直接访问到私有属性// person3._height = 555; 这个代表动态的插入一个可以直接访问到的属性_height,和 构造函数中的 var _height 不是一个。// console.log(person3._height); //undefined//通过自己写的一个set和get访问器来访问_heightconsole.log(person3.getHeight());person3.setHeight(555);console.log(person3.getHeight());//使用静态方法Person.staticFn();//通过静态方法创建对象//静态方法创建对象,纯属是为了简化代码,其核心内部还是用new去创建对象var person4 = Person.createPerson("尔泰", 88);person4.sayHello();</script></body></html>
【原型模式创建对象】
每个构造函数都有一个原型属性(prototype),这个原型属性指向一个原型对象,可以给这个原型对象声明属性和方法,这个属性和方法可以给通过构造函数创建出来的对象实例使用。换句话说,也可以理解为,每个构造函数创建出来的对象实例,继承于这个原型对象的属性和方法。【总结】所谓原型模式创建对象其核心还是通过构造函数创建对象,只不过方法是通过原型模式创建的。
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>原型模式创建对象</title><script type = "text/javascript">//狗的构造函数function Dog (name) {this.name = name;// this.bark = function () {// console.log("旺旺旺");// }//全局函数虽然能解决函数共享的问题,但不适合// this.bark = globalFn;}//弄一个全局函数,这样可以解决每次创建对象都开辟一个空间去存储function。function globalFn () {console.log("汪汪汪");}//通过Dog的原型对象声明bark方法,这样可以达到函数共享,节约性能Dog.prototype.bark = function () {console.log("原型 汪汪汪");}//创建一只狗的对象var dog1 = new Dog("妞妞");//在创建一只狗的对象var dog2 = new Dog("Nimo");//验证一下这两只狗的bark方法是否指向同一个function对象的方法console.log(dog1.bark === dog2.bark);</script></head><body></body></html>
全局属性类似全局变量,只不过全局变量是window的属性,而全局属性是某个创建对象的属性
// 全局变量var x = 3;console.log(x); // 在全局环境中可以访问console.log(window.x);// 全局属性function Person () {this.name = "我是全局属性";}var xiaoMing = new Person();console.log(xiaoMing.name); // 在全局环境中可以访问
类似于局部变量,不能在全局环境中正常访问到
function Person () {var name = "我是私有属性";// 不是指私有于某个具体对象// 是指在访问权限受限,不能被正常访问// 可以通过 getter 和 setter 来访问和操作}
自定义属性访问器
function Person (name, age) {//声明全局属性//this代表当前对象实例this.name = name;this.age = age;//声明一个私有属性//私有属性就是外部函数无法直接访问到var _height = 172;//模拟JS中set 和 get访问器 来访问私有属性//设值方法 (访问私有变量)this.setHeight = function (height) {_height = height;}//取值方法 (访问私有变量)this.getHeight = function () {return _height;}}
原生JS定义访问器方法
Object.defineProperty(this,"私有变量名",{ 字面量对象的方法 });
用的时候直接正常调用即可
```javascript// 人的构造函数function Person() {// 私有属性var name = "能看见我不???";// 给当前对象的name属性添加 set 和 get 访问器或构造器/*** [set description]***/Object.defineProperty(this,"name",{set : function(newName){// 其牛逼之处!!能filterif(newName === "小明"){name = newName;return;}console.log("不好意思,我们只要小明!");},get : function(){console.log("这是name属性的get访问器");return name;}});}
每一个构造函数都有一个原型属性(prototype),这个原型属性指向一个原型对象,可以给这个原型对象声明属性和方法,该属性和方法可以给通过构造函数创建出来的对象实例使用.换句话说,也可以理解为,每个构造函数创造出来的对象实例,继承于原型对象的属性和方法.所谓原型模式创建对象的核心还是通过构造行数创建对象,只不过方法是通过原型模式创建的.
每个对象都有一个原型对象,而原型对象它也是个对象,既然是对象也会相应的原型对象,以此类推,直到Object.prototype. Object.prototype它的原型是null(到头了).
prototype有一个属性
__proto__
例如: a对象是b对象的原型对象,b对象是c对象的原型对象,那么a/b/c就在一条原型链了,那么abc对象都可以访问原型链上的任何对象和属性.
// 猫的构造函数function Cat() {}// 动物的构造函数function Animal(){this.name = "动物";}// 给Animal构造函数设置原型对象Animal.prototype = {sex: "女",sayHello : function(){console.log("大家好");}};// 给Cat构造函数设置原型对象 (绑在了原型链子上)Cat.prototype = new Animal(); // 为了简化开发// 创建一只猫的对象var cat1 = new Cat();// cat1对象先去查找自身构造函数是否有name/sex等属性和方法,如果有就直接用自己,如果没有,一直沿着原型链找到Object.prototype为止.如果还没有,就会报undefined;console.log(cat1.name); // 访问的是Animal的name属性console.log(cat1.sex);cat1.sayHello();
// {} 表示字面量创建对象,其内部的语法符合JSON语法格式Animal.prototype = {sex: "女",sayHello : function(){console("大家好");}};
函数表达式的创建方法,每新new一个对象就会默认添加一个prototype属性,同时添加一个constructor属性,而用对象字面量的方式相当于完全重写了prototype对象,所以constructor属性指向了Object,而不再指向它的构造函数了!!!!
添加 constructor : 构造函数名来修正这个问题
// {} 表示字面量创建对象,其内部的语法符合JSON语法格式Animal.prototype = {constructor : Animal; // 修正指向的问题sex: "女",sayHello : function(){console("大家好");}};
总结 : 编程时务必要遵守。下文都遵循这一点,即如果替换了prototype对象,
O.prototype = {};
那么,下一步必然是为新的prototype对象加上constructor属性,并将这个属性指回原来的构造函数。
O.prototype.constructor = O;// 或O.prototype = {constructor : O,};
每个对象都有一个constructor属性,这个属性默认是指原型对象所在的构造函数.
// 人的构造函数function Person(){}// 给人的原型对象设置一个方法Person.prototype.sayHello = function(){console.log("大家好");}// 创建一个人的对象var person1 = new Person();// 测试console.log(Person.prototype.constructor);/*person1本身没有constructor属性,是通过原型链往上找的,找到Person.prototype里才发现有的*/console.log(person1.constructor);
判断某个对象是否存在某个属性.
// > > 接上面// 验证了constructor只是prototype原型链中的属性console.log(person1.hasOwnProperty("constructor")); // falseconsole.log(Person.prototype.hasOwnProperty("constructor")); //true
返回一个对象的原型对象
// 返回person1的原型对象console.log(Object.getPrototypeOf(person1));
根据接收参数对象,来创建一个新的对象,前者是这个新对象的原型.
// 字面量创建一个对象var o1={};// 通过Object.create()方法创建新的对象// 内部的实现是 new + 构造函数名var o2 = Object.create(o1);var o3 = Object.create(o2);
判断一个对象是否是另外一个对象的原型对象.
// 判断o1是否是o2的原型对象console.log(o1.isPrototypeOf(o2));console.log(o1.isPrototypeOf(o3));
- 写在私有属性的下面
 - 参数1: this
 - 参数2: "私有属性变量名"
 - 参数3: {}里面是个json的对象
 
// 人的构造函数function Person() {// 私有属性var name = "能看见我不???";// 给当前对象的name属性添加 set 和 get 访问器或构造器/*** [set description]***/Object.defineProperty(this,"name",{set : function(newName){// 其牛逼之处!!能filterif(newName === "小明"){name = newName;return;}console.log("不好意思,我们只要小明!");},get : function(){console.log("这是name属性的get访问器");return name;}});}// 创建一个人的对象var person1 = new Person();console.log(person1.name); // 取值操作//person1.name = "小红"; // 赋值操作 (是错的)person1.name = "小明"; // 赋值操作 (是对的)console.log(person1.name); // 取值操作
需求 : 需要猫去继承动物,如下代码如何实现??
// 构造函数之动物function Animal(){this.species = "动物";}// 构造函数之猫 (猫是动物的子类)function Cat(name,color){this.name = name;this.color = color;}
方法一 : 构造函数绑定
第一种方法也是最简单的方法,使用call或apply方法,将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行:
function Cat(name,color){Animal.apply(this, arguments); // 继承的关键this.name = name;this.color = color;}var cat1 = new Cat("大毛","黄色");alert(cat1.species); // 动物
方法二 : prototype模式
第二种方法更常见,使用prototype属性。如果"猫"的prototype对象,指向一个Animal的实例,那么所有"猫"的实例,就能继承Animal了。
Cat.prototype = new Animal(); // 实现继承的关键Cat.prototype.constructor = Cat; // 修正constructor问题var cat1 = new Cat("大毛","黄色");alert(cat1.species); // 动物
方法三 : 直接继承prototype
第三种方法是对第二种方法的改进。由于Animal对象中,不变的属性都可以直接写入Animal.prototype。所以,我们也可以让Cat()跳过 Animal(),直接继承Animal.prototype。但是也带来了相关问题
function Animal(){ }Animal.prototype.species = "动物";// 将Cat的prototype对象,然后指向Animal的prototype对象,这样就完成了继承。Cat.prototype = Animal.prototype;Cat.prototype.constructor = Cat;var cat1 = new Cat("大毛","黄色");alert(cat1.species); // 动物/*与前一种方法相比,这样做的优点是效率比较高(不用执行和建立Animal的实例了),比较省内存。缺点是 Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。所以,上面这一段代码其实是有问题的。请看第三行Cat.prototype.constructor = Cat;这一句实际上把Animal.prototype对象的constructor属性也改掉了!alert(Animal.prototype.constructor); // Cat*/
方法四 : 利用空对象做中介
由于"直接继承prototype"存在上述的缺点,所以就有第四种方法,利用一个空对象作为中介。
var F = function(){}; // 空对象F.prototype = Animal.prototype;Cat.prototype = new F(); // 空对象是"中介"Cat.prototype.constructor = Cat;// F是空对象,所以几乎不占内存。这时,修改Cat的prototype对象,就不会影响到Animal的prototype对象。alert(Animal.prototype.constructor); // Animal
方法四:封装成一个函数来使用
// 我们将上面的方法,封装成一个函数,便于使用。function extend(Child, Parent) {var F = function(){};F.prototype = Parent.prototype;Child.prototype = new F();Child.prototype.constructor = Child;Child.uber = Parent.prototype;}// 使用的时候,方法如下extend(Cat,Animal);var cat1 = new Cat("大毛","黄色");alert(cat1.species); // 动物// 这个extend函数,就是YUI库如何实现继承的方法。// 另外,说明一点,函数体最后一行Child.uber = Parent.prototype;/*意思是为子对象设一个uber属性,这个属性直接指向父对象的prototype属性(uber是一个德语词,意思是"向上"、"上一层")。这等于在子对象上打开一条通道,可以直接调用父对象的方法。这一行放在这里,只是为了实现继承的完备性,纯属备用性质。*/
方法五 : 拷贝继承
上面是采用prototype对象,实现继承。我们也可以换一种思路,纯粹采用"拷贝"方法实现继承。简单说,如果把父对象的所有属性和方法,拷贝进子对象,不也能够实现继承吗?这样我们就有了第五种方法。
// Animal的所有不变属性,都放到它的prototype对象上function Animal(){}Animal.prototype.species = "动物";// 然后,再写一个函数,实现属性拷贝的目的。function extend2(Child, Parent) {var p = Parent.prototype;var c = Child.prototype;for (var i in p) {c[i] = p[i];}c.uber = p;}/*这个函数的作用,就是将父对象的prototype对象中的属性,一一拷贝给Child对象的prototype对象。使用的时候,这样写:*/extend2(Cat, Animal);var cat1 = new Cat("大毛","黄色");alert(cat1.species); // 动物
比如 实现字面两对象的继承 举例: 医生是中国人
// 字面量对象 "中国人"var Chinese = {nation:'中国'};// 字面量对象 "医生"var Doctor ={career:'医生'}
方法一 : object()方法
方法二 : 浅拷贝
方法三 : 深拷贝
/*作业 1 ( 版本1 ) :构造函数:长方形(有参数)公有属性:长:length宽:width求面积的方法。(原型)*/function Rectangle ( length , width ) {// 公有属性this.length = length;this.width = width;}// 面积的方法Rectangle.prototype.area = function () {return this.length * this.width; // 对象中的this}// 创建一个长方形对象实例var rect1 = new rectangle(20,30); // 可以直接传参给// rect1.length = 20; // 可以后给属性赋值// rect1.width = 30; // 可以后给属性赋值console.log(rect1.area()); // 因为this,所以....
/*作业 1 (版本2) :构造函数:长方形(无参数)私有属性:长:length宽:width属性的set 和 get访问器求面积的方法。(原型)*/// 创建长方型的类function Rectangle () {var length = 0;var width = 0;// length 属性访问器Object.defineProperty(this,"length",{set : function (x) {length = x;},get : function () {return length;}});// width 属性访问器Object.defineProperty(this,"width",{set : function (x) {width = x;},get : function () {return width;}});}// 求面积的方法Rectangle.prototype.area = function () {return this.length * this.width; // 对象中的this}// 创建一个长方形实例var rect1 = new Rectangle() ;rect1.length = 30 ;rect1.width = 20 ;console.log(rect1.area());
/*作业 2 :构造函数:银行账户私有属性:账户名,存储金额属性访问器:存款和取款的方法(原型)*/// 创建银行账户类function BankAccount (name , money) {this.accoutName = name;this.money = money;}// 存款方法BackAccount.prototype.saveMoney = function (saveMoney) {console.log("您的" + this.accountName + "当前余额为:" + this.money+"元");this.money += saveMoney;console.log("您的" + this.accountName + "存款之后余额为:" + this.money + "元");}// 取款方法BankAccount.prototype.takeMoney = function (takeMoney) {if(takeMoney > this.money){console.log("您的账户余额不足"+"最多能取"+this.money+"元");return ;}if(takeMoney < 0){console.log("请不要逗我玩")return;}console.log("您的"+this.accountName+"当前余额为:"+this.money+"元");this.money -= takeMoney; // 注意-=而不是赋值console.log("您的" + this.accountName + "取款之后的余额为:" + this.money+"元");}// 创建银行账户实例var GivenCui_account = new BankAccount("Given Cui",100000);// 存款GivenCui_account.saveMoney(50000); // 150000GivenCui_account.saveMoney(12000); // 162000// 取款GivenCui_account.takeMoney(300);GivenCui_account.takeMoney(500000); // 真实的需求
以对象作为做小单位设计的好处
1. 方便以后的程序的功能扩展
2. 子功能的添加独立于其它功能
3. 更有利于团队的协同开发,有封装的思想
/*作业 3 :构造函数:汽车、公路私有属性:速度(汽车)路程长度(公路)属性访问器:业务方法:求车跑在路上所有时长*/// 汽车类function Car (speed) {// 公有属性this.speed = speed;}// 车在路上的方法_star/*// 第一种写法// 普通的传参Car.prototype.carOnRoad = function (length) {return length / this.speed;}*/// 第二种写法// 彻底的面向对象是指所有的基本单位都是对象// 所以 参数 ===>对象.属性 , 所以把对象作为参数传入Car.prototype.carOnRoad = function (road) {return road.length / this.speed;}// 车在路上的方法_end// 公路的类function Road (length) {// 公有属性this.length = length;}// 路的对象实例var road1 = new Road(100);// 车的对象实例var car1 = new Car(20);// 求解需求// console.log(car.carOnRoad(road1.length)) // 调用第一种方法console.log(car.carOnRoad(road1)) // 调用第二种方法
/*作业 4 :一个人手里有两张牌,左手红桃A,右手方片K,现在让这个人交换手里的两张牌。构造函数:人、牌私有属性:左手 :(人,注意:左手就是一个字符串,表示左手抓的牌是什么花色和大小)右手 :(人,注意:同上)牌的花色号码(牌)业务方法:抓拍、交换拍、展示牌*/// 创建类"人"function Person (l_poker,r_poker) {var leftHand = l_poker.pokerType;var rightHand = r_poker.pokerType;// leftHand访问器Object.defineProperty(this, "leftHand", {set : function (x) {leftHand = x;// return // 这里不用写return},get : function () {return leftHand;}});// rightHand访问器Object.defineProperty(this,"rightHand", {set : function (x) {rightHand = x;},get : function () {return rightHand;}});}// 抓牌的方法// 传入扑克牌对象Person.prototype.fetchPoker = function (l_poker ,r_poker ) {// 传入了牌对象的pokerType属性this.leftHand = l_poker.pokerType ;this.rightHand = r_poker.pokerType ;}// 展示牌的方法// 不需要参数Person.propotype.showPoker = function () {console.log("左手的牌是" + this.leftHand + ",右手的牌是:" + this.rightHand);}// 交换牌的方法Person.propotype.swapPoker = function () {// 中间值互换var temp = this.leftHand;this.leftHand = rightHand;this.rightHand = temp;// 位操作互换}// 创建类"poker"function Poker () {> var pokerType = '';// pokerType访问器Object.defineProperty(this, "pokerType", {set : function (x) {pokerType = x;} ,get : function () {return pokerType;}});}// 创建扑克牌对象实例var poker1 = new Poker ("红桃A");var poker2 = new Poker ("方片K");var poker3 = new Poker ("大鬼");var poker4 = new Poker ("小鬼");// 创建人对象实例var xm = new Person (poker1, poker2); // 小明 ==> xm// 展示牌xm.showPoker();// 抓牌xm.fetchPoker(poker3, poker4);// 再显示xm.showPoker();// 交换牌xm.swapPoker();