JavaScript 深入理解对象知识点

深入理解对象

1.JavaScript工厂模式

## 工厂模式就是创建对象的一种方式

## 作用:创建对象;降低代码冗余度。

##创建对象的方式:

a.用字面量的方式创建对象
var person={
  name:'zhangsan',
  age:20,
  gender:'male',
  sayName:function(){
     console.log(this.name);
  }
}
缺点:这个对象是一次性的,如果有四十给同学,这个代码就要写四十次。
b.new Object()创建对象
var person=new Object();
person.name='zhangsan';
person.age:33
person.gender='male';
person.sayName=function(){
  console.log(this.name)
}
缺点:先实例化了一个对象,然后再为对象添加属性,看不出来是一个整体。


## 使用工厂模式创建对象

//将创建对象的代码封装在一个函数中
function createPerson(name, age, gender) {
  var person = new Object();
  person.name = name;
  person.age = age;
  person.gender = gender;
  person.sayName = function () {
    console.log(this.name);
  }
  return person;
}
//利用工厂函数来创建对象
var person1 = createPerson("zhangsan", 18, 'male');
var person2 = createPerson("lisi", 20, 'female');
缺点:这种方式本质上是将创建对象的过程进行了封装,本质并没有改变,我们创建一个student时无法知道其具体的数据类型,
只知道这是一个对象,往往实际开发中我们需要确定这个对象到底是个Person的实例还是Dog的实例。

2.构造函数模式

a.自定义构造函数

// 自定义构造函数
function Person(name, age, gender) {
  this.name = name;  //相当于person1.name/person2.name
  this.age = age;
  this.gender = gender;
  this.sayName = function () {
    console.log(this.name);
  }
}

var person1 = new Person('zhangsan', 29, 'male');
var person2 = new Person('lisi', 19, 'female');
1.创建了一个obj实例
2.this指向到person1  
3.执行函数内代码
4.返回obj实例  
有new就是新创建一个实例对象 person1和person2不是同一个实例 有不同的内存地址  
所以person1.sayName()不等于person2.sayName()

person1.sayName(); // zhangsan
person2.sayName(); // lisi
在这个案例中,Person()构造函数代替了 createPerson()工厂函数。实际上,Person()内部的代码跟 createPerson()基本是一样的,只是有如下区别。
- 没有显式地创建对象。
- 属性和方法直接赋值给了 this。
- 没有 return。
注意:按照惯例,构造函数名称的首字母都是要大写的,非构造函数则以小写字母开头。


b.创建Person实例

只要使用了new操作符调用的函数就是一个构造函数
创建Person的实例:用new操作符
var person1 = new Person('zhangsan', 29, 'male');//person1为Person的实例
(1) 在内存中创建一个新对象。
(2) 这个新对象内部的[[Prototype]]特性被赋值为构造函数的 prototype 属性。
(3) 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)。
(4) 执行构造函数内部的代码(给新对象添加属性)。
(5) 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。
person1 和 person2 分别保存着 Person 的不同实例。所有对象都会从它的原型上继承一个 `constructor` 属性,这两个对象的constructor 属性指向 Person,
console.log(person1.constructor === Person); // true  person1是创建出来的实例,实例调用原型对象中的constructor指回构造函数
console.log(person2.constructor === Person); // true

c.instanceof

instanceof运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。或者说判断一个对象是某个对象的实例。
console.log(person1 instanceof Object); // true
console.log(person1 instanceof Person); // true
console.log(person2 instanceof Object); // true
console.log(person2 instanceof Person); // true
person1 和 person2 之所以也被认为是 Object 的实例,是因为所有自定义对象都继承自 Object。

d.使用函数表达式自定义构造函数
赋值给变量的函数表达式也可以表示构造函数:
var Person = function (name, age, gender) {
  this.name = name;
  this.age = age;
  this.gender = gender;
  this.sayName = function () {
    console.log(this.name);
  };
}
var person1 = new Person("zhangsan", 29, "male");
var person2 = new Person("lisi", 27, "female");
person1.sayName(); // zhangsan
person2.sayName(); // lisi
console.log(person1 instanceof Object); // true
console.log(person1 instanceof Person); // true
console.log(person2 instanceof Object); // true
console.log(person2 instanceof Person); // true
构造函数后面的括号可加可不加。只要有 new 操作符,就可以调用相应的构造函数


e.构造函数也是函数

使用 new 操作符调用就是构造函数,而不使用 new 操作符调用的函数就是普通函数。
var Person = function (name, age, gender) {
  this.name = name;
  this.age = age;
  this.gender = gender;
  this.sayName = function () {
    console.log(this.name);
  };
}
// 1.作为构造函数
var person = new Person("Jacky", 29, "male");
person.sayName(); // Jacky
// 2.作为函数调用
Person("lisi", 27, "female"); // 添加到全局对象 node global 浏览器 window
global.sayName(); // lisi
// 3.在另一个对象的作用域中调用
var o = new Object();
Person.call(o, "wangwu", 25, "male");
o.sayName(); // wangwu
在调用一个函数而没有明确设置 this 值的情况下(即没有作为对象的方法调用,或者没有使用call()/apply()调用):
*node中:this 始终指向 Global 对象
*在浏览器中:this指向window对象


f.构造函数的问题

ECMAScript 中的函数是对象,每次定义函数时,都会初始化一个对象
因此对前面的案例而言,person1 和 person2 都有名为 sayName()的方法,但这两个方法不是同一个Function 实例。
这个构造函数实际上是这样的:
function Person(name, age, gender) {
  this.name = name;
  this.age = age;
  this.gender = gender;
  this.sayName = new Function("console.log(this.name)"); // 逻辑等价
}
console.log(person1.sayName == person2.sayName); // false
有new就是新创建一个实例对象 person1和person2不是同一个实例 有不同的内存地址  所以person1.sayName()不等于person2.sayName()

因为都是做一样的事,所以没必要定义两个不同的 Function 实例。况且,this 对象可以把函数与对象的绑定推迟到运行时。
把函数定义转移到构造函数外部:
function Person(name, age, gender) {
  this.name = name;
  this.age = age;
  this.gender = gender;
  this.sayName = sayName;
}
function sayName() {
  console.log(this.name);
}
var person1 = new Person("zhangsan", 29, "male");
var person2 = new Person("lisi", 27, "female");
person1.sayName(); // zhangsan
person2.sayName(); // lisi

因为这一次 sayName 属性中包含的只是一个指向外部函数的指针,所以 person1 和 person2共享了定义在
全局作用域上的 sayName()函数。
这样虽然解决了相同逻辑的函数重复定义的问题,但全局作用域也因此被搞乱了。

3.原型模式

a.原型层级

找属性:先在实例里找 -> 再到原型对象找 -> 实例的原型对象的实例的原型对象找 -> null
hasOwnProperty():查看是否是实例属性
delete操作符可以完全删除实例上的这个属性

function Person() { }
Person.prototype.name = "zhangsan";
Person.prototype.age = 29;
Person.prototype.gender = "male";
Person.prototype.sayName = function () {
  console.log(this.name);
};
var person1 = new Person();
var person2 = new Person();
// 通过hasOwnProperty()可以查看访问的是实例属性还是原型属性
console.log(person1.hasOwnProperty('name')); //false

person1.name = "lisi";
console.log(person1.name); // lisi,来自实例
//只在重写 person1 上 name 属性的情况下才返回 true,表明此时 name 是一个实例属性,不是原型属性
console.log(person1.hasOwnProperty('name')); //true

console.log(person2.name); // zhangsan,来自原型

console.log(person2.hasOwnProperty('name'));//false

delete person1.name;//删除实例中的name
console.log(person1.name); // zhangsan,来自原型

console.log(person1.hasOwnProperty('name'));//false


b.原型与in操作符

属性在原型上或者在实例上都会返回true
访问到的属性属于原型属性的方法:
function Person() { }
Person.prototype.name = "zhangsan";
Person.prototype.age = 29;
Person.prototype.gender = "male";
Person.prototype.sayName = function () {
  console.log(this.name);
};
var person1 = new Person();
var person2 = new Person();

// 无论属性是在实例上还是在原型上,都可以检测到
console.log("name" in person1); // true
console.log("name" in person2); // true
// 判断一个属性是否是原型属性
function hasPrototypeProperty(object, name) {
  //不在实例中但是可以访问到的属性属于原型属性
  return !object.hasOwnProperty(name) && (name in object);
}
console.log(hasPrototypeProperty(person1, 'name'));//true


c.原生对象的原型

引用类型的构造函数(包括 Object、Array、String 等)都在原型上定义了实例方法。
通过原生对象的原型可以取得所有默认方法的引用,也可以给原生类型的实例定义新的方法。
实例添加了一个 last()方法:
//给字符串添加属性或方法  要写到对应的包装对象的原型下才行
var str = 'hello';
String.prototype.last = function () {
  // 返回指定位置的字符
  return this.charAt(this.length - 1);
};
console.log(str.last()); // o
注意:尽管可以这么做,但并不推荐在产品环境中修改原生对象原型。这样做很可能造成误会,而且可能引发命名冲突。另外还有可能意外重写原生的方法。


d.更简单的原型模式

在前面的案例中,每次定义一个属性或方法都会把 Person.prototype 重写一遍。为了减少代码冗余,也为了
从视觉上更好地封装原型功能:
function Person() {}
Person.prototype = {
  name: "zhangsan",
  age: 29,
  gender: "male",
  sayName() {
    console.log(this.name);
  }
};
// 在这个案例中,Person.prototype 被设置为等于一个通过对象字面量创建的新对象。最终结果是一样的,只有一个问题:这样重写之后,Person.prototype 的 constructor 属性就不指向 Person了。在创建函数时,也会创建它的 prototype 对象,同时会自动给这个原型的 constructor 属性赋值。而上面的写法完全重写了默认的 prototype 对象,因此其 constructor 属性也指向了完全不同的新对象(Object 构造函数),不再指向原来的构造函数。
var person1 = new Person()
console.log(person1.constructor === Person); //false
console.log(person1.constructor === Object); //true

解决constructor不再指向原来的构造函数的问题:
Object.defineProperty()方法来定义 constructor 属性:
function Person() { }
Person.prototype = {
  //这种方式恢复 constructor 属性会创建一个[[Enumerable]]为 true 的属性
  //constructor: Person,
  name: "zhangsan",
  age: 29,
  gender: "male",
  sayName() {
    console.log(this.name);
  }
};
// 恢复 constructor 属性
Object.defineProperty(Person.prototype, "constructor", {
  enumerable: false,
  value: Person
});
var person1 = new Person()
console.log(person1.constructor == Person); //true
console.log(person1.constructor == Object); //false


e.原型的问题

function Person() { }
Person.prototype = {
  constructor: Person,
  name: "zhangsan",
  friends: ["lisi", "wangwu"],
  sayName() {
    console.log(this.name);
  }
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("zhaoliu");
console.log(person1.friends); // [ 'lisi', 'wangwu', 'zhaoliu' ]
console.log(person2.friends); // [ 'lisi', 'wangwu', 'zhaoliu' ]
console.log(person1.friends === person2.friends); // true
person1.friends 通过 push 方法向数组中添加了一个字符串。
由于这个friends 属性存在于Person.prototype 而非 person1 上,
新加的这个字符串也会在(指向同一个数组的)person2.friends 上反映出来。

4.组合模式

组合使用构造函数模式和原型模式。构造函数用于定义实例属性,原型模式用于定义方法和共享属性。这种模式是目前在ECMAScript中使用最广泛,认同度最高的一种创建自定义类型的方法。

function Person(name, age, gender) {
  this.name = name;
  this.age = age;
  this.gender = gender;
  this.firends = ['zhangsan', 'lisi'];
}
Person.prototype = {
  constructor: Person,
  sayName: function () {
    console.log(this.name);
  }
};
var p1 = new Person('larry', 44, 'male');
var p2 = new Person('terry', 39, 'male');

p1.firends.push('robin');
console.log(p1.firends); // [ 'zhangsan', 'lisi', 'robin' ]
console.log(p2.firends); // [ 'zhangsan', 'lisi' ]
console.log(p1.firends === p2.firends); // false
console.log(p1.sayName === p2.sayName); // true

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值