@GivenCui
2016-05-28T14:13:54.000000Z
字数 14819
阅读 768
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); //true
console.log(person1 instanceof Object); //true
console.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访问器来访问_height
console.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){
// 其牛逼之处!!能filter
if(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")); // false
console.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){
// 其牛逼之处!!能filter
if(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); // 150000
GivenCui_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();