1.JS对象创建的三种方式
1.第一种形式 工厂模型
function createPerson(name , sex , age){
var obj = new Object();
obj.name = name ;
obj.sex = sex ;
obj.age = age ;
obj.sayName = function(){
alert(this.name);
}
return obj;
}
var p1 = createPerson('z3' , '男' , 20);
var p2 = createPerson('z4' , '女' , 25);
//alert(p1.sex);
p1.sayName();
```
2. 构造函数式, 例如new Array new Date
// 函数的第一个字母大写 (类的模版)
function Person(name , age , sex){
this.name = name ;
this.age = age ;
this.sex = sex ;
this.sayName = function(){
alert(this.name);
}
}
//构造一个对象 new关键字 传递参数 执行模版代码 返回对象
var p1 = new Person('小1' , 20 , '男');
var p2 = new Person('小2' , 21 , '女');
//alert(p1.name);
//p1.sayName();
//alert(p1 == p2); //类的概念:根据模版创建出不同的实例对象
//alert(p1.constructor == Person);
//alert(p2.constructor == Person);
alert(p1 instanceof Person);
alert(p1 instanceof Object);
3.创建对象的方式:
// 1当作构造函数去使用 :
var p1 = new Person('小1' , 20 , '男');
// 2作为普通的函数去调用
Person('小2' , 25 , '男'); //在全局环境里定义属性并复制 直接定义在window上
//alert(name); // 小2
// 3在另一个对象的作用域中调用
var o = new Object();
// call applly
Person.call(o,'小4' , 12 , '女');
alert(o.name); // 小4
#####2.原型 prototype
原型:原型对象里的所有属性和方法被所有构造函数实例化出来的对象所共享
//使用构造函数方式创建对象
function Person(name , age){
this.name = name ;
this.age = age ;
this.sayName = function(){alert('我是姓名!')};
}
var p1 = new Person('z3',20);
var p2 = new Person('z4',21);
p1.sayName(); // 我是姓名!
p2.sayName(); // 我是姓名!
alert(p1.sayName == p2.sayName); // false 每次创建对象都新建一个函数(实例化一个方法)
alert(p1.prototype == p2.prototype); // true
可以这样避免每次创建对象都新建一个函数(实例化一个方法)
function Person(name , age){
this.name = name ;
this.age = age ;
this.sayName = sayName ;
}
// 定义了一次函数
function sayName(){
alert(this.name);
}
var p1 = new Person('z3',20);
var p2 = new Person('z4',21);
p1.sayName(); // z3
p2.sayName(); // z4
alert(p1.sayName == p2.sayName); // true
alert(p1.name == p2.name); // false
> prototype 创建每一个函数都有一个prototype属性,这个属性其实是一个指针,而这个指针总指向一个对象
这个对象的用途就是将特定的属性和方法包含在内,起到一个所有实例所共享的作用
利用原型对象避免 使用构造行数创建对象时没次都新建一个函数
function Person(){
}
var obj = Person.prototype;
alert(typeof obj); //object
// 公共属性 name age sayName
obj.name = 'z3';
obj.age = 20 ;
obj.sayName = function(){alert(this.name);};
var p1 = new Person();
var p2 = new Person();
alert(p1.age); // 20
alert(p2.age); // 20
p1.sayName(); // z3
p2.sayName(); // z3
alert(p1.sayName == p2.sayName); // true 可见对象是使用一个函数创建而来
p1.name = 'lisi';
alert(p2.name); // z3 不影响对象
关于JS原型, 每一个对象都有prototype属性,该属性指向原型对象。 每个对象共享原型对象中的属性
![原型图例](http://upload-images.jianshu.io/upload_images/1278799-d856eec34d339504.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
构造函数 原型对象 实例对象的关系
构造函数.prototype = 原型对象
原型对象.constructor = 构造函数
实例对象.prototype = 原型对象
>isPrototypeOf(); 判断原型的方法
>Object.getPrototypeOf() 根据实例对象获得原型对象
每次当代码读取一个对象的属性的时候,首先会进行一次搜索,搜索实例对象里name的属性,看看有没有。如果没有,再去实例所对应的原型对象里去搜索name属性,如果有则返回, 没有返回undefined
>hasOwnProperty(属性名) 判断一个对象的属性是属于原型属性 还是属于实例属性
function Person(){
}
// 添加原型属性
Person.prototype.name = 'z3';
Person.prototype.age = 20;
Person.prototype.sayName = function () {
alert('我是原型对象的方法');
};
// 用Person实例化的所有对象都共享属性 name age sayName
var p3 = new Person();
alert(p3.hasOwnProperty('name')); // false
>in 操作符 判断属性是否存在于实例对象或原型对象中 如果其中一个存在就返回true
var p1 = new Person();
alert('name' in p1); // true
var p2 = new Person();
p2.name = 'w3';
alert('name' in p2); // true
所以 判断一个属性是否存在原型中,可以使用 hasOwnProperty和in结合判断
// object:当前对象 name:要判断的属性
function hasPrototypeProperty(object, name) {
return !object.hasOwnProperty(name) && name in object;
}
> Object.keys() 拿到当前对象里的所有keys 返回一个数组(不可被枚举的constructor属性除外)
> Object.getOwnPropertyNames() 枚举对象所有的属性,不管该内部属性能否被枚举
var p1 = new Person();
p1.name = 'z3';
p1.age = 20;
var attributes = Object.keys(p1);
alert(attributes); //name,age
var attributes2 = Object.keys(Person.prototype);
alert(attributes2); //name,age,sayName 原型对象的属性数组
// constructor属性, 该属性是不能被枚举的,[eable = false]
// Object.getOwnPropertyNames() 枚举对象所有的属性,不管该内部属性能否被枚举
var attributes3 = Object.getOwnPropertyNames(Person.prototype);
alert(attributes3); //constructor,name,age,sayName
> 使用原型扩展对象中的属性和方法
例如:模拟Array中的each循环方法
Array中的forEach方法,只适合遍历一维数组, 使用原型扩展一个each方法,完成遍历多维数组
var arr = [1,2,3,[4,[5]]];
arr.forEach(function (item, index, array) {
alert(item); // 1 2 3 4,5
});
使用原型对象实现一个Array的each方法,遍历多维数组
var arr = [1,2,3,[4,[5,[6]]]];
// 在原型对象上添加方法
Array.prototype.each = function (fn) {
try{
// 目的:遍历数组的每一项
this.i || (this.i = 0); // 计数器,记录当前元素的位置 没有使用var i= 0; 是为了避免变量冲突
// 严谨的判断:什么时候走each核心方法
// 当数组的长度大于0时并且传递的参数必须为函数fn.constructor == Function
if(this.length > 0 && fn.constructor == Function) {
// 循环遍历数组的每一项
while(this.i < this.length){ // 底层代码少写forin循环 避免变量冲突
// 核心代码, 获取数组的每一项
var e = this[this.i];
// 如果当前元素获取到了 并且是一个数组
if(e && e.constructor == Array){
//直接做递归操作
e.each(fn);
} else {
// 如果不是数组(单个的元素)
//fn(e);
//var obj = true;
//fn.apply(obj, [e]);
//这的目的就是为了把数组的当前元素传递给fn函数 并让函数执行
//fn.apply(e, [e]);
fn.call(e,[e]);
}
this.i++;
}
this.i == null; // 释放内存 垃圾回收机制回收变量
}
} catch(ex){
// do something
}
return this; // this 指向方法的调用者
}
// 运行
arr.each(function (item) {
alert(item); // 1 2 3 4 5 6
});
>简单原型
直接通过对象字面量来重写整个原型对象(这种方法会改变原型对象的构造器)
function Person(){
}
Person.prototype = { // 给原型对象重新设置了一个匿名方法,原型对象的构造器不再是Person(){};
// 需要给原型对象添加构造器
// constructor:Person, // 将构造器属性直接写在这里,这样不行,以为constructor是不可枚举的,在这里可以枚举得到
name:'z3',
age:20,
job:'程序员',
say: function () {
alert('我是原型的函数!');
}
};
// 使用Object.defineProperty()方法给原型对象重新设置构造器
// 参数1: 重设构造器的对象 参数2:设置什么属性 参数3:option配置项
Object.defineProperty(Person.prototype, 'constructor', {
enumerable: false,
value:Person
});
原型具有动态性,在普通原型中可以先创建对象,再添加原型属性,例如:
function Person(){
}
var p1 = new Person();
// 原型对象的构造器默认为Person
Person.prototype.say = function () {
alert('我是方法!')
};
p1.say(); // 我是方法!
但是使用简单原型时,是不可以先创建对象后添加原型属性的
function Person(){
}
var p1 = new Person(); // 原型对象是空的
Person.prototype = { // 给原型对象重新设置了一个匿名方法,原型对象的构造器不再是Person(){};
say: function () {
alert('我是原型的函数!');
}
};
Object.defineProperty(Person.prototype, 'constructor', {
enumerable:false,
value: Person
});
p1.say(); //爆错: p1.say is not a function(…) 因为原型对象里面没有任何属性和方法
// 注意:简单原型的使用顺序(实例对象必须在原型对象之后创建)
var p2 = new Person();
p2.say(); // 我是原型的函数!
>原型的弊端和创建对象的方式
原型里的属性和方法被所有对象所共享,修改一个对象的属性,两个一个对象的属性也变了
function Person(){
}
Person.prototype = {
name:'z3',
age:20,
job:'程序员',
friends:['李四', '王五'],
sayName: function () {
alert('我的名字!')
}
};
Object.defineProperty(Person.prototype, 'constructor', {
enumerable: false,
value:Person
})
var p1 = new Person();
var p2 = new Person();
p1.friends.push('赵六');
alert(p2.friends); // 李四,王五,赵六
// 原型里的属性和方法被所有对象所共享, static的
创建对象的方式:
1.组合使用原型和构造函数式 (定义一个类,开发常用的方法)
function Person(name, age, friends, job) {
this.name = name;
this.age = age;
this.friends = friends;
this.job = job;
}
Person.prototype = {
sayName: function () {
alert(this.name);
}
};
Object.defineProperty(Person.prototype, 'constructor', {
enumerable: false,
value:Person
})
var p1 = new Person('z3', 20, ['w5', 'z6'], '技术总监');
var p2 = new Person('l4', 18, ['w5', 'z6'], 'boss');
p1.friends.push('赵六');
alert(p1.friends); // w5, z6, 赵六
alert(p2.friends); // w5, z6
2. 动态原型模式(让属性和方法 都封装到一起)
function Person(name, age, friends, job) {
this.name = name;
this.age = age;
this.friends = friends;
this.job = job;
// 动态原型方法
if(typeof this.sayName != 'function'){
Person.prototype.sayName = function () {
alert(this.name);
}
}
}
3.稳妥构造函数式:durable object(稳妥对象) 在非常安全的环境中
特点:1.没有公共属性 2.不能使用this对象
function Person(name, age, job){
// 创建一个要返回的对象
var obj = new Object();
// 可以定义一些私有的变量和函数
var sex = '男';
var saySex = function () {};
var name = name;
// 添加对外方法
obj.sayName = function () {
alert(name);
}
return obj;
}
var p1 = new Person('z3',20,'程序员');
p1.sayName(); // 只能通过方法访问名字
#####3.继承
######JS采用原型链的概念实现继承
构造函数、原型对象和实例对象三者之间的关系有:
1. 构造函数.prototype = 原型对象
2. 原型对象.constructor = 构造函数(模版)
3. 原型对象.isPrototypeOf(实例对象),判断实例对象的原型 是不是当前对象
4. 构造函数与实例对象是类和实例的关系
根据它们的关系实现JS的继承
// 父类构造函数
function Sup(name){
this.name = name;
}
// 父类原型对象
Sup.prototype = {
sayName: function () {
alert(this.name);
}
}
Object.defineProperty(Sup.prototype, 'constructor', {
enumerable:false,
value: Sup
});
//子类构造函数
function Sub(age){
this.age = age;
}
// 让子类的原型对象等父类的实例,结果会怎么样? 实现JS的继承
// 1.此时的原型对象包含一个指向另一个原型的指针
// sup的实例对象 和 sup的原型对象 有一个关系
// 2.相应的另一个原型中也包含了一个指向另一个构造函数的指针
// 子类的原型对象的构造器变成了父类的构造器
alert(Sub.prototype.constructor); // 结果 function Sub(age){this.age = age;}
Sub.prototype = new Sup('z3');
alert(Sub.prototype.constructor); // 结果 function Sup(name){this.name = name;}
var sub1 = new Sub(19);
alert(sub1.name); // z3
alert(sub1.age); // 19
sub1.sayName(); // z3
sub1.name = 'lisi';
sub1.sayName(); // lisi
######继承的方式
1.原型继承 即继承了父类的模板也继承了父类的原型对象
// 父类
function Person(name, age){
this.name = name;
this.age = age;
}
// 父类的原型对象属性
Person.prototype.id = 10;
//子类
function Boy(sex){
this.sex = sex;
}
// 继承已经实现了
Boy.prototype = new Person('z3');
var b = new Boy();
alert(b.name); // z3
alert(b.id); // 10
2.类继承 只继承模板不继承原型对象(借用构造函数方式继承)
function Person(name, age){
this.name = name;
this.age = age;
}
// 父类的原型对象属性
Person.prototype.id = 10;
//子类
function Boy(name, age, sex){
// call apply
Person.call(this, name, age);
this.sex = sex;
}
var b = new Boy('z3', 20, '男');
alert(b.name); // z3
alert(b.age); // 20
alert(b.sex); // 男
alert(b.id); // undefined 父类的原型对象并没有继承
3.原型继承+借用构造函数 = 混合继承
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.id = 10;
Person.prototype.sayName = function(){alert(this.name);};
function Boy(name, age, sex){
// call apply
Person.call(this, name, age); // 1.借用构造函数继承,复制了父类的模板
this.sex = sex;
}
// 2.原型继承
Boy.prototype = new Person(); //继承父类的原型对象和复制了父类的模板
//存在的缺点就是复制了两次父类的模板
4.混合继承
function extend(sub ,sup){
// 目的: 实现只继承父类的原型对象
var F = new Function(); // 1 创建一个空函数 目的:空函数进行中转
F.prototype = sup.prototype; // 2 实现空函数的原型对象和超类的原型对象转换
sub.prototype = new F(); // 3 原型继承
sub.prototype.constructor = sub ; // 4还原子类的构造器
//保存一下父类的原型对象: 一方面方便解耦 另一方面方便获得父类的原型对象
sub.superClass = sup.prototype; //自定义一个子类的静态属性 接受父类的原型对象
//判断父类的原型对象的构造器 (加保险)
if(sup.prototype.constructor == Object.prototype.constructor){
sup.prototype.constructor = sup ; //手动欢迎父类原型对象的构造器
}
}
// 混合继承:原型继承和借用构造函数继承
function Person( name , age){
this.name = name ;
this.age = age ;
}
Person.prototype = {
constructor: Person ,
sayHello: function(){
alert('hello world!');
}
};
function Boy(name , age , sex){
//call 绑定父类的模版函数 实现 借用构造函数继承 只复制了父类的模版
Boy.superClass.constructor.call(this , name , age);
this.sex = sex ;
}
//原型继承的方式: 即继承了父类的模版 又继承了父类的原型对象
//Boy.prototype = new Person();
// 只继承一遍父类的原型对象
extend(Boy , Person);
// 给子类加了一个 原型对象的方法
Boy.prototype.sayHello = function(){
alert('hi javascript!');
}
var b = new Boy('张三' , 20 , '男');
alert(b.name);
alert(b.sex);
//b.sayHello();
Boy.superClass.sayHello.call(b);
//alert(Boy.superClass.constructor);
// 混合继承的缺点: 3件事 : 继承了父类的2次模版 , 继承了一次父类的原型对象
// extend方法 2件事: 继承1次父类的模版 继承一次父类的原型对象
参考白贺翔教程