文章目录
JS中创建对象的三种方式
- 利用 new Object()创建对象
- 利用对象字面量 {} 创建对象
- 利用functon className() 结合 new className创建对象
// 1. 利用 new Object() 创建对象
var obj1 = new Object();
// 2. 利用 对象字面量创建对象
var obj2 = {};
// 3. 利用构造函数创建对象
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function() {
console.log('我会唱歌');
}
}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
console.log(ldh);
ldh.sing();
zxy.sing();
静态成员和实例成员
静态成员
- 静态成员是在构造函数上直接添加的,只能通过构造函数来访问
Star.sex = "男"
实例成员
- 实例成员就是构造函数内部通过this添加的成员 uname age sing 就是实例成员,实例成员只能通过实例化的对象来访问
// 构造函数中的属性和方法我们称为成员, 成员可以添加
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function() {
console.log('我会唱歌');
}
}
var ldh = new Star('刘德华', 18);
// 1.实例成员就是构造函数内部通过this添加的成员 uname age sing 就是实例成员
// 实例成员只能通过实例化的对象来访问
console.log(ldh.uname);
ldh.sing();
// console.log(Star.uname); // 不可以通过构造函数来访问实例成员
// 2. 静态成员 在构造函数本身上添加的成员 sex 就是静态成员
Star.sex = '男';
// 静态成员只能通过构造函数来访问
console.log(Star.sex);
console.log(ldh.sex); // 不能通过对象来访问
prototype原型对象
构造函数中存在的问题
- 构造函数中成员变量实例方法的内存空间是不会共用的,这就会造成内存泄漏的问题。
- 解决:通过将方法绑定到构造函数中的prototype对象上实现方法的内存共用,确保构造函数实例化出来的不同对象共用的是一个方法。
/**
* 在使用构造函数时,如果存在成员实例方法,因为方法是一个复杂对象,每次通过构造函数创建一个新的对象时,都会会这个复杂对象(方法)开辟一个新的内存空间存放,这样就会存在内存泄漏的问题
* 解决:通过讲方法存放到prototype(原型对象上),做到所有对象公用一个内存地址的方法,节省内存
* 在JS中,每一个构造函数中都会默认有一个prototype,指向另一个对象,这个对象的所有属性和方法,都会被构造函数所拥有
* 而对于实例的对象,都会默认有一个 __proto__ 属性,该属性指向的就是构造函数中的prototype,二者等价,所以对象可以使用prototype中的方法和属性
* 方法的查找规则: 首先先看ldh 对象身上是否有 sing 方法,如果有就执行这个对象上的sing
* 如果么有sing 这个方法,因为有__proto__ 的存在,就去构造函数原型对象prototype身上去查找sing这个方法
* 注意:不论sing在ldh实例对象上或者是__proto__原型对象上,sing中的this始终指向ldh这个实例对象,因为他是调用者。
* @param uname
* @param age
* @constructor
*/
// 构造函数中的属性和方法我们称为成员, 成员可以添加
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function () {
console.log('我会唱歌');
}
}
Star.prototype.jump = function () {
console.log('I can jump')
};
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 20);
console.dir(Star); //可以查看Star中的prototype对象
console.log(ldh);
console.log(ldh.sing === zxy.sing); // false,该方法没有绑定到prototype上,每次实例化对象都会开辟新的内存存储该方法
console.log(ldh.jump === zxy.jump); // true,该方法有绑定到prototype上,每次实例化对象都会重复使用prototype中的jump
// 调用的模式和sing一样
ldh.jump();
zxy.jump();
// 对象原型
// 方法的查找规则: 首先先看ldh 对象身上是否有 sing 方法,如果有就执行这个对象上的sing
// 如果么有sing 这个方法,因为有__proto__ 的存在,就去构造函数原型对象prototype身上去查找sing这个方法
console.log(ldh.__proto__ === Star.prototype);// true,二者等价
console.log("============================");
console.log(Star.prototype);
console.log(ldh.__proto__)
对象原型
-
对于构造函数中实例的对象,都会默认有一个 proto 属性,该属性指向的就是构造函数中的prototype,二者等价,所以对象可以使用prototype中的方法和属性
-
方法的查找规则,首先看本对象上是否有该成员(变量或方法),有则引用,没有则会在 proto 原型对象上寻找该成员。
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype.sing = function() {
console.log('我会唱歌');
};
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
ldh.sing();
console.log(ldh); // 对象身上系统自己添加一个 __proto__ 指向我们构造函数的原型对象 prototype
console.log(ldh.__proto__ === Star.prototype);
// 方法的查找规则: 首先先看ldh 对象身上是否有 sing 方法,如果有就执行这个对象上的sing
// 如果么有sing 这个方法,因为有__proto__ 的存在,就去构造函数原型对象prototype身上去查找sing这个方法
对象实例-原型对象-构造函数的关系
- 构造函数中protottpe属性,指向原型对象
- 实例中 _proto_属性,指向原型对象
- 原型对象中contrustor属性,指向构造函数
使用构造函数+原型对象模仿类的继承
- 在构造函数中使用call方法,模拟super关键字,继承父类中成员变量
- 将字类的原型对象指向父类的实例,并添加属性constructor
指向字类的构造函数
function Father(uname, age) {
this.uname = uname;
this.age = age;
this.sleep = function () {
console.log("Father's sleep")
}
}
Father.prototype.money = function () {
console.log("Father's money")
};
function Son(uname, age, school) {
// 这里借用了call修改Father中的this指向为son对象,从而达到给son赋值属性的目的
Father.call(this, uname, age);
this.school = school;
}
// 这样继承到Father类中原型对象的方法,而通过call可以继承Father原型类中的实例方法和属性
Son.prototype.study = function () {
console.log("Son's study")
};
Son.prototype.__proto__ = Father.prototype; // constructor和实例对象都有原型对象(prototype和__proto___指向的是同一个原型对象),
// 而原型对象也是一个对象,对象都拥有一个原型对象。而prototype中的原型对象__proto__指的就是他的父类的原型对象
Son.prototype.smile = function () {
console.log("Son's smile")
};
var son = new Son("周文军", 20, "复旦");
// print(son.uname); // 调用print方法,打印机
// print(son.age);
console.log(son.uname);
console.log(son.age);
console.log(son);
console.log(son.__proto__);
son.money();
son.study();
son.sleep();
son.smile();
console.log(Father.prototype)
console.log(son);
console.log("================================老师的继承");
// Son.prototype = Father.prototype; 这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化
Son.prototype = new Father(); //这样存在问题,每次继承的原型对象中的方法,都会是father中的实例方法,虽然能做到后面的都共享,
// 但是每次都会new一个Father对象。并且新添加的方法只可能在new操作后面执行。
// 这种继承方式的另一个缺陷就是Father中的成员变量 name,age会以undefined的形式存在在Son的prototype中
// 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数 !!!!!!!
Son.prototype.constructor = Son;
// 这个是子构造函数专门的方法
Son.prototype.exam = function() {
console.log('孩子要考试');
};
var son2 = new Son('刘德华', 18, 100);
console.log(son2);
son2.exam();
console.log(Father.prototype);
console.log(Son.prototype.constructor);
var son3 = new Son('张学友', 18, 100);
son3.exam()
原型链
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype.sing = function() {
console.log('我会唱歌');
}
var ldh = new Star('刘德华', 18);
// 1. 只要是对象就有__proto__ 原型, 指向原型对象
console.log(Star.prototype);
console.log(Star.prototype.__proto__ === Object.prototype);
// 2.我们Star原型对象里面的__proto__原型指向的是 Object.prototype
console.log(Object.prototype.__proto__);
// 3. 我们Object.prototype原型对象里面的__proto__原型 指向为 null
原型链中成员的查找规则
- 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
- 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)。
- 如果还没有就查找原型对象的原型(Object的原型对象)。
依此类推一直找到 Object 为止(null)。 - __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype.sing = function() {
console.log('我会唱歌');
};
Star.prototype.sex = '女';
Star.prototype.favorate = '唱跳rap篮球';
// Object.prototype.sex = '男';
var ldh = new Star('刘德华', 18);
ldh.sex = '男';
console.log(ldh.favorate); // 唱跳rap篮球
console.log(ldh.sex); // 男
console.log(Object.prototype);
console.log(ldh);
console.log(Star.prototype);
console.log(ldh.toString());
原型对象中的this指向问题
- 在构造函数中,里面this指向的是对象实例 ldh
- 原型对象函数里面的this 指向的是 实例对象 ldh
var that_;
function Star(uname, age) {
console.log("function",this); // 对象实例 ldh
that_ = this;
this.uname = uname;
this.age = age;
}
var that;
Star.prototype.sing = function() {
console.log('我会唱歌');
that = this;
};
var ldh = new Star('刘德华', 18);
// 1. 在构造函数中,里面this指向的是对象实例 ldh
ldh.sing();
console.log(that === ldh); // true
console.log(that === that_); //true
// 2.原型对象函数里面的this 指向的是 实例对象 ldh
利用原型对象扩展内置对象的方法
// 原型对象的应用 扩展内置对象方法
Array.prototype.sum = function() {
var sum = 0;
for (var i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
};
// Array.prototype = { // 错误写法!!!
// sum: function() {
// var sum = 0;
// for (var i = 0; i < this.length; i++) {
// sum += this[i];
// }
// return sum;
// }
// }
var arr = [1, 2, 3];
console.log(arr.sum());
console.log(Array.prototype);
var arr1 = new Array(11, 22, 33);
console.log(arr1.sum());
ES6中类的本质
- 类的本质其实就是一个函数,因此他也包含prototype,
并且此prototype中的constructor指向的就是这个类本身,而通过类创建的对象则会含有__proto__原型对象
// ES6 之前通过 构造函数+ 原型实现面向对象 编程
// (1) 构造函数有原型对象prototype
// (2) 构造函数原型对象prototype 里面有constructor 指向构造函数本身
// (3) 构造函数可以通过原型对象添加方法
// (4) 构造函数创建的实例对象有__proto__ 原型指向 构造函数的原型对象
// ES6 通过 类 实现面向对象编程
class Star {
}
console.log(typeof Star); // function
// 1. 类的本质其实还是一个函数 我们也可以简单的认为 类就是 构造函数的另外一种写法
// (1) 类有原型对象prototype
console.log(Star.prototype); // Object
// (2) 类原型对象prototype 里面有constructor 指向类本身
console.log(Star.prototype.constructor);
// (3)类可以通过原型对象添加方法
Star.prototype.sing = function() {
console.log('冰雨');
}
var ldh = new Star();
console.dir(ldh);
// (4) 类创建的实例对象有__proto__ 原型指向 类的原型对象
console.log(ldh.__proto__ === Star.prototype);
i = i + 1;
i++