6.1理解对象
创建对象的方法
1.创建Object实例
var person = new Object()
person.name = "chu"
2.对象字面量语法
var person = {
name:"chu"
}
属性类型
数据类型与访问器属性
1.数据属性
var person = {}
Object.defineProperty(person,"name",{
writable:false,
value:"doraemon"
})
console.log(person.name) //doraemon
person.name = "chu"
console.log(person.name) //doraemon
在调用Object.defineProperty()方法时,如果不指定,configurable,enumerable 和 writable 特性的默认值都是false
2.访问器属性
访问器属性不包含数据值,包含一对 getter 和 setter 函数。
getter : 读取访问器属性时,会调用getter函数,这个函数负责返回有效的值
setter:在写入访问器属性时,会调用setter函数并传入新值,这个函数负责决定如何处理数据
访问器属性不能直接定义,必须啊使用Object.defineProperty() 来定义
备注:下面这个例子没懂
var book = {
_year: 2004,
edition: 1
}
Object.defineProperty(book, "year", {
get: function () {
return this._year
},
set: function (newValue) {
if (newValue > 2004) {
this._year = newValue
this.edition += newValue - 2004
}
}
})
book.year = 2005
console.log(book.edition)
6.2创建对象
工厂模式
解决了创建多个相似对象的问题,但是没有解决对象识别的问题(即怎样知道一个对象的类型)
function createPerson(name,age,job){
var o = {}
o.name = name
o.age = age
o.job = job
o.sayName = function(){
console.log(this.name)
}
return o
}
var person1 = createPerson("chu",18,"coder")
console.log(person1)
构造函数模式
创建自定义的构造函数,从而定义自定义对象类型的属性和方法
function Person(name,age,job){
this.name = name
this.age = age
this.job = job
this.sayName = function(){
console.log(this.name)
}
}
var person1 = new Person("chu",18,"coder")
console.log(person1)
两者的区别
- 没有显示的创建对象
- 直接将属性和方法赋给了this对象
- 没有return 语句
- 构造函数的函数名 使用的是首字母大写
什么是构造函数
任何函数,只要通过new操作符来调用,那它就可以作为构造函数
使用new操作符,构造函数经历了几个步骤
- 创建一个新对象
- 将构造函数的作用域赋给新对象 (因此 this 就指向了这个新对象)
- 执行构造函数中的代码(为这个新对象添加属性)
- 返回新对象
构造函数的优缺点
创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型
缺点:每个方法都要在每个实例上重新创建一遍 ,以这种方式创建函数,会导致不同的作用域链和标识符解析,但创建Function 新实例的机制依然是相同的。
每新建一个实例,就会新建一个方法,没有必要,因为所有方法都是同样的行为,完全应该共享
改进 :把 sayName() 函数 的定义转义到构造函数外部
问题: 在全局作用域中定义的函数实际上只能被某个对象调用,这让作用域有点名副其实。如果对象需要定义很多方法,那么就要定义很多个全局函数
function Person(name,age,job){
this.name = name
this.age = age
this.job = job
this.sayName = sayName
}
function sayName(){
console.log(this.name)
}
var person1 = new Person("chu",18,"coder")
person1.sayName()
构造函数的用法场景
原型模式
我们创建的每一个函数都有一个 prototype (原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。
function Person(){
}
Person.prototype.name = "chu"
Person.prototype.age = 18
Person.prototype.job = "coder"
Person.prototype.sayName = function(){
console.log(this.name)
}
var person1 = new Person()
var person2 = new Person()
console.log(person1)
与构造函数模式不同的是,新对象的属性和方法是由所有实例共享的。
person1和person2 访问的都是同一组属性和同一个sayName() 函数。
1.原型对象
Person.prototype.constructor === Person) //true
Person 的每个实例—person1 person2 都包含一个内部属性,该属性仅仅指向了Person.prototype,
它们与构造函数没有直接的关系。
注意:虽然这两个实例都不包含属性和方法,但我们却可以调用person1.sayName() 这是通过查找对象属性的过程来实现的。
可以通过isPrototypeOf() 方法来确定对象之间是否存在这种关系。从本质上讲,如果[[ Prototype ]] 指向调用 isPrototypeOf() 方法的对象 (Person.prototype) 那么这个方法就返回true。因为person1 和 person2 内部都有一个指向Person.prototype的指针
ES5新增 Object.getPrototypeOf()
- 可以通过对象实例访问保存在原型中的值,但是却不能通过对象实例重写原型中的值,
- 如果我们在实例中添加了一个属性,而该属性与实例中的一个属性同名,那么我们就在实例中创建该属性,该属性将会屏蔽原型中的那个属性。
- 使用 delete 操作符可以完全删除实例属性 从而可以重新访问原型中的属性
function Person(){}
Person.prototype.name = 'chu'
Person.prototype.age = 29
Person.prototype.job = 'coder'
Person.prototype.sayName = function(){
console.log(this.name)
}
var person1 = new Person()
var person2 = new Person()
person1.name = "doraemon"
console.log(person1.name) //doraemon
console.log(person2.name) //chu
- hasOwnPrototype() 指示对象自身属性中是否具有指定的属性
通过使用 hasOwnPrototype() 重写了name属性后才会返回true,因为这时候name才是一个实例属性,而非原型属性。
2.原型与in操作符
in 操作符只要通过对象能够与访问到属性就返回true hasOwnProperty() 只在属性存在于实例中时才返回true.
因此 只要in操作符 返回 true 而 hasOwnProperty() 返回 false ,就可以确定属性是原型中的属性
function hasPrototypeProperty(object,name){
return !object.hasOwnProperty(name) && (name in object)
}
通俗点说 这个方法就是判断 是不是自己又重新写了构造函数的属性,还是直接是用的原型上的属性
如果是直接原型上的属性,那么返回的就是true
3.更简单的原型语法
前面的方法每添加一个属性和方法都需要Preson.prototype 。常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象
注意 : 此处的constructor 属性不在指向Person了,这里的语法完全重写了默认的prototype对象,因此constructor 属性也变成了新对象的constructor 属性(指向Object构造函数)
function Person(){}
Person.prototype = {
name:"chu",
age:18,
job:"coder"
}
console.log(friend.constructor == Person) //false
console.log(friend.constructor == Object) //true
4.原型的动态性
这里我们知道 new操作符中的步骤。调用构造函数时会为实例添加一个指向最初原型的[[prototype]]指针,而把原型修改为另一个对象(也就是上面代码的顺序)就等于切断了构造函数与最初原型之间的联系。
*记住:实例中的指针仅指向原型,而不指向构造函数
function Person(){
}
var friend = new Person() *
Person.prototype = { *
name:"chu"
}
console.log(friend.name) //undefiend
5.原生对象的原型
原生模型的重要性不仅体现在创建自定义类型方面,就连所有原生的引用类型,都是采用这种模式创建的,
所有原生引用类型(Object,Array,String)等都在其构造函数的原型上定义了方法。
console.log(typeof Array.prototype.sort) //function
console.log(typeof String.prototype.substring) //function
通过原生对象的原型,不仅可以取得所有默认方法的引用,而且也可以定义新方法,可以像修改自定义对象的原型一样修改原生对象的原型,因此可以随时添加方法。
注意:不推荐这样做,可能会导致命令冲突或者重写原生方法。
String.prototype.startsWith = function(text){
return this.indexOf(text) == 0
}
var msg = "hello world"
console.log(msg.startsWith("hello")) //true
6.原型对象的问题
- 首先省略了构造函数传递参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。
- 原型模式最大的问题是由其共享的本性所导致
- 这种共享对于函数非常合适,但是对于包含引用类型值的属性来说就有问题了。
function Person() {
}
Person.prototype = {
name:"chu",
firends:["bob","tom"]
}
var person1 = new Person()
var person2 = new Person()
person1.firends.push("chu")
console.log(person1.firends) //["bob", "tom", "chu"]
console.log(person2.firends) //["bob", "tom", "chu"]
组合使用构造函数模式和原型模式
- 这种构造函数与原型混成的模式,可以说是定义引用类型的一种默认模式。
- 创建自定义类型的最常见方式就是组合使用构造函数模式与原型模式,构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。
- 好处是每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度的节省了内存。
function Person(name){
this.name = name
}
Person.prototype = {
// constructor:Person,
sayName(){
console.log(this.name)
}
}
var person1 = new Person("chu")
console.log(person1.sayName === person2.sayName) //true
6.3继承
实现继承主要是依靠原型链来实现的。利用原型让一个引用类型继承另一个引用类型的属性和方法
原型链
构造函数,原型,实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
function SuperType(){
this.property = true
}
SuperType.prototype.getSuperValue = function(){
return this.property
}
//继承了 SuperType
function SubType(){
this.subproperty = false
}
SubType.prototype = new SuperType()
SubType.prototype.getSubValue = function(){
return thos.subproperty
}
var instance = new SubType()
console.log(instance.getSuperValue()) //true
确定原型和实例的关系
- instanceof 操作符
- 只要用这个操作符来测试实例与原型中出现过的构造函数,结果就会返回true
- isPrototypeOf 方法
- 只要原型链中出现过的原型,都可以说是该原型链所派生的实例的原型
借用构造函数
组合继承
思路:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。
缺点:无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
function SuperType(name){
this.name = name
this.colors = ["red","blue","green"]
}
SuperType.prototype.sayName = function(){
console.log(this.name)
}
function SubType(name,age){
SuperType.call(this,name)
this.age = age
}
SubType.prototype = new SuperType()
SubType.prototype.sayAge = function(){
console.log(this.age)
}
var instance1 = new SubType("chu",18)
instance1.colors.push("black")
console.log(instance1.colors)
instance1.sayName()
instance1.sayAge()
简化版:
function SuperType(name){
this.name = name
}
function SubType(name,age){
SuperType.call(this,name)
this.age = age
}
//这是需要继承方法的时候才会写
// SubType.prototype = new SuperType()
var a = new SubType("chu",18)
console.log(a.name)
寄生组合式继承
function inheritPrototype(subType, superType) {
var prototype = Object(superType.prototype) //创建对象
prototype.constructor = subType //增强对象
subType.prototype = prototype //指定对象
}
function SuperType(name) {
this.name = name
this.colors = ["red", "blue", "green"]
}
SuperType.prototype.sayName = function () {
console.log(this.name)
}
function SubType(name, age) {
SuperType.call(this, name)
this.age = age
}
inheritPrototype(SubType, SuperType)
SubType.prototype.sayAge = function () {
console.log(this.age)
}
对象
存在值简写
"key" in object
in的左边必需是属性名,通常是一个字符串,如果不用字符串,那就是一个字符串变量
for … in 循环
为了使用对象所有的属性,就可以利用for…in循环。
语法:
for(key in object){
//各个属性键值得执行区
}
注意,所有的 “for” 都允许我们在循环中定义变量,像 let key 这样。
同样,我们可以用其他属性名来代替 key。例如 "for(let prop in obj)" 也很常用。
引用复制
对象是按引用存储复制的 即 变量存储的不是对象本身,而是对象的”内存地址”,是对象的引用
也就是当对象被复制的时候, 引用被复制了一份 对象并没有被复制。
原始类型:字符串 数字 布尔类型 是被整个赋值的
比较引用
等号 == 和严格等 === 对于对象来说没有差别
当两个引用指向同一个对象的时候他们相等。
常量对象
一个被const 修饰的对象可以被修改
因为 对象存储的是引用地址,只要不改变引用地址就可以进行修改。
复制与合并
let user = {
name:"chu",
age:18
}
let clone = {}
for(let key in user){
clone[key] = user[key]
}
clone.name = "pete"
console.log(user.name) //chu
Object.assign(dest,[src1,src2…])
let user = {name:"chu"}
let permissions1 = {vanView:true}
let permissions2 = {vanEdit:true}
let newObject = {}
Object.assign(newObject,permissions1,permissions2)
我们可以用 Object.assign 来代理简单的复制方法:
let user = {
name: "John",
age: 30
};
let clone = Object.assign({}, user);
但是无法进行深拷贝
js 实现的库 lodash _.cloneDeep(obj)
判断对象中是否有属性 检查空对象
let user = {}
for(let key in user){
//此时是没有任何输出的
return ...
}
return ...
//真实例子
function isEmpty(obj){
for(let key in obj){
return "有"
}
return "没有"
}
let schedule = {}
schedule.name = 'chu'
console.log(isEmpty(schedule))
属性值求和
let salaries = {
John:100,
chu:1000,
alice:200
}
// 先检查是否为空对象
let sum = 0;
for(let key in salaries){
sum+=salaries[key]
}
console.log(sum)
数值属性都乘以2
**
let menu = {
width:200,
height:300,
title:"my menu"
}
function multiplyNumeric(obj){
for(let key in obj){
if(typeof(obj[key]) == "number"){
// obj[key] = obj[key]*2
obj[key] *= 2
}
}
return obj
}
multiplyNumeric(menu)