分针网—每日分享:图例详解那道setTimeout与循环闭包的经典面试题

来源:互联网 发布:初学者吉他 知乎 编辑:程序博客网 时间:2024/05/18 15:29
简单创建对象

var o = new Object();
o.name = 'hang';
o.age = 21;
o.sayName = function(){
console.log(this.name);
};
o.sayName(); // 'hang'
//使用字面量创建,和以上代码相等
var o={
name:'hang',
age:21,
sayName:funciont(){
console.log(this.name);
}
};
o.sayName(); // 'hang'

  使用以上的方法创建对象很简单,但是缺点也是非常明显的,如果再次创建相同属性和方法的对象的时候,还要把代码复制一遍,会导致很多代码重复。

工厂模式

function createObj(name,age){
var o=new Object();
o.name=name;
o.age=age;
o.sayName=function(){
console.log(this.name);
}
return o;
}
var p1=createObj('xuhang',21);
var p2=createObj('Tomoya',22);
p1.sayName(); // 'xuhang'
p2.sayName(); // 'Tomoya'
console.log(typeof p1); // object
p1 instanceof createObj //false 无法判断类型

  用工厂模式来创建对象,实现了一套属性和方法可以实现多个对象可以使用,可以传递参数,但主要缺点是无法识别对象类型。

构造函数模式

function CreateObj(name,age){
this.name=name;
this.age=age;
this.sayName=function(){
console.log(this.name);
};
}
var p1=new CreateObj('xuhang',21);
var p2=new CreateObj('Tomoya',22);
p1.sayName(); // xuhang
console.log(p1 instanceof CreateObj); // true 可以判断类型

用构造函数模式,解决了工厂模式中不能判断对象类型的问题,还有必须要用new 关键字来创建对象。new关键字相当于执行了一下几步。

1.创建一个新对象
2.将构造函数的作用域赋值给这个新对象(因此this就是指向了这个新对象)
3.执行这个构造函数的代码(为这个对象添加属性)
4.返回新对象

  使用构造函数创建的对象都有一个constructor属性,该属性可以标识对象类型,但一般我们还是常用instanceof来判断对象类型, 因为constructor属性仅返回构造函数类型。


p1.constructor === CreateObj //true
p1.constructor === Object //false

p1 instanceof createObj // true
p1 instanceof Object //true

不使用new时,函数的作用域会是全局作用域,this就会指向window对象。

  构造函数也存在问题,每个方法都要在实例上创建一遍。也就是说p1和p2的sayName()方法虽然作用相同,但这两个方法并不是同一个函数。

p1.sayName==p2.sayName//false

原型模式

  原型模式解决了构造函数模式中同功能的方法的代码无法重用的问题。

  我们创建的每个函数都有一个名为prototype(原型)的属性,这个属性是一个指针,指向一个对象,这个对象被称为原型对象。原型对象有一个名叫constructor的属性,这个属性是一个指针,指向构造函数。

  使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。换句话说,不必再构造函数中定义对象实例的信息,而是可以将这些信息添加到原型对象中。

function CreateObj(){
CreateObj.prototype.name = 'xuhnag';
CreateObj.prototype.age = 21;
CreateObj.prototype.sayName = function(){
return this.name;
};
}
var p1 = new CreateObj();
p1.name; //'xuhang'
var p2 = new CreateObj()
p2.name; //'xuhang'

  说明一下以上例子,p1的name属性,如果自身有name属性,就会返回自身的值,如果没有的话,则查找原型对象中有没有name属性,若原型对象中有name属性,则返回其值,否则,就报错。

p1.name='许航';
p1.name;// 许航
p2.name;// xuhang

  可以通过hasOwnProperty()方法检测一个属性是存在于具体的对象中,还是存在于该对象的原型对象中。

p1.hasOwnProperty('name'); //true
p2.hasOwnProperty('name'); //false

  也可以通过 in 关键字来判断一个属性是否存在于具体的对象或者该对象的原型对象中。

'name' in p1 // true
'name' in p2 // true

   原型模式存在的问题,它省略了为构造函数传递初始化参数,这给程序上造成了一定的不便,另外最主要的是,当我们改变值为引用类型的对象的属性时,这个改变的结果会被其他对象共享。

function CreateObj(){
}
CreateObj.prototype={
constructor:CreateObj, //将原型对象重写(指向Object)
name:'xuhang',
age:21,
family:['爸爸','妈妈'],
sayName:function(){
console.log(this.name);
}
};
var p1=new CreateObj();
var p2=new CreateObj();
p1.family.push('妹妹');
console.log(p2.family); // '爸爸','妈妈','妹妹'

构造函数和原型模式结合

  构造函数模式的缺点就是无法共享方法,原型模式的方法没问题,缺点就是当原型对象属性的值引用类型时,对其进行修改会反映到所有实例中。

  将两者结合起来,对象的属性使用构造函数的模式创建,对象的方法用原型模式来创建。

function CreateObj(name.age){
this.name=name;
this.age=age;
this.family=['爸爸','妈妈'];
}
CreateObj.prototype={
constructor:CreateObj,
sayName:function(){
console.log(this.name);
}
}
var p1=new CreateObj('xuhang',21);
var p2=new CreateObj('Tomoya',22);
p1.family.push("妹妹");
console.log(p1.family); // '爸爸','妈妈','妹妹'
console.log(p2.family); // '爸爸','妈妈'

JavaScript面向对象-继承
原型链继承

  非常简单的继承关系,实例是子类的实例,也是父类的实例。父类新增的原型属性和原型方法,子类都能访问到。原理:是将父类的实例作为子类的原型。

function Cat(name,age){
this.name=name;
this.age=age;
this.showName=function(){
console.log("我叫"+name+",你们好!");
}
}
Cat.prototype.showEat=function(food){
console.log(this.name+"正在吃"+food);
}
var cat1=new Cat('Ami',2);
function Dog(){
}
Dog.prototype=new Cat(); //将父类的实例作为子类的原型
var dog=new Dog('jod',3); // 无法向父类传递参数;
console.log(dog.showName());

  但是缺点也是很明显,要想为子类新增属性和方法,必须要在new Cat()这样的语句之后执行,不能放到构造器中。创建子类实例时,无法向父类构造函数传参。

  还有个致命的缺点,当我们改变值为引用类型的原型对象的属性时,这个改变的结果会被所有子对象共享。

构造继承

  解决了原型链继承中,子类实例共享父类引用属性的问题创建子类实例时,可以向父类传递参数可以实现多继承(call多个父类对象)。原理:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

function Cat(name,age){
this.name=name;
this.age=age;
this.showName=function(){
console.log("我叫"+name+",你们好!");
}
}
Cat.prototype.showEat=function(food){
console.log(this.name+"正在吃"+food);
}
function Dog(name,age){
Cat.call(this,name,age); // 继承属性
}
var dog1=new Dog('job',3);
var dog2=new Dog('job',2);
dog1.showName(); //我叫job,你们好!
dog1.showEat('鱼'); // 报错
console.log(dog1 instanceof Cta); // false
console.log(dog1.showName === dog2.showName)// false

  缺点有如下几点,实例并不是父类的实例,只是子类的实例。只能继承父类的实例属性和方法,不能继承原型属性/方法。无法实现函数复用,父类中的方法在每个子类中都会生成一遍,父类中的方法没有被复用。

实例继承

  不限制调用方式,返回的对象具有相同的效果。也可以继承原型属性/方法。
原理:是作为子类实例返回

function Cat(name,age){
this.name=name;
this.age=age;
this.showName=function(){
console.log("我叫"+name+",你们好!");
}
}
Cat.prototype.showEat=function(food){
console.log(this.name+"正在吃"+food);
}
function Dog(name,age){
var instance=new Cat();
instance.name=name;
instance.age=age;
return instance;
}
var dog=new Dog('job',3);
console.log(dog instanceof Cat); // true;
console.log(dog instanceof Dog); // false;
dog.showEat('fish'); // 可以使用原型方法

  缺点是,实例是父类的实例,不是子类的实例,不支持多继承。

组合继承

  组合继承就是将原型链继承和借用构造方法继承组合,发挥两者之长。原理:通过调用父类构造,继承父类的属性并保留传参,然后通过将父类实例作为子类原型。

可以继承实例属性/方法,也可以继承原型属性/方法。
既是子类的实例,也是父类的实例。
不存在引用属性共享问题。
可传参,函数可复。

function Cat(name,age){
this.name=name;
this.age=age;
this.showName=function(){
console.log("我叫"+name+",你们好!");
}
}
Cat.prototype.showEat=function(food){
console.log(this.name+"正在吃"+food);
}
function Dog(name,age){
// 引用父类型的属性,又调用了一次父函数
Cat.call(this,name,age);
}
// 继承父类型的方法,调用了一次父函数
Dog.prototype=new Cat();
var dog=new Dog('job',3);
console.log(dog instanceof Cat); //true;
console.log(dog instanceof Dog); //true;
dog.showName();
dog.showEat('fish');

  组合继承没什么太大的问题,只是调用了两次父类构造函数,仅仅消耗多了点内存。


转载:http://xhwhen.coding.me/2017/05/07/JavaScript面向对象笔记和总结/
本文转载自分针网




想学习更多IT知识 可加群:272292492

0 0