JS高级:对象

认识对象:
什么是对象:对象就是一组没有顺序的数据的集合,对象的每个属性或者方法都有映射的值
最简单的创建对象的方法:自定义一个对象,然后追个给对象添加属性和方法。栗子:
(1)Object构造函数方法:

 var obj = new Object()
    obj.name = 'pipi'
    obj.sayName = function () {
        console.log(this.name)
    }
    obj.sayName() // pipi

适用于:起始时不知道对象内部有哪些属性
弊端:语句太多,解决方式:对象字面量方法

(2)对象字面量的方法:

  var obj = {
        name:'pipi',
        sayName: function () {
            console.log(this.name)
        }
    }
   obj.sayName() // pipi
   
   var obj1 = {
        name:'啊哈',
        sayName: function () {
            console.log(this.name)
        }
    }
  obj1.sayName() // 啊哈

适用于:起始时,对象内部数据是确定的
弊端:如果创建多个相似的对象,代码重复,如果只创建一个就没问题

1.对象的数据属性和访问器属性

(1)数据属性:–4个
a。[[configurable]]——属性是否可以通过delete删除,默认值为true
b.[[enumerrable]]——属性是否可以通过for-in遍历读到,默认为true
c。[[writable]]——属性是否被改写,默认值为true
d。[[value]]——属性默认值,默认为undefined
(注:使用双方括号,代表是内部的属性,只有js引擎能使用,而JavaScript不能直接访问这些属性

上述4个数据属性是对象每个属性特有的,修改属性的特性值必须通过特定的函数——Object.defineProperty()
修改方式:
Object.defineProperty(属性所在的对象,属性名,要修改的数据属性值的对象形式)
栗子:

 	var obj = {
        name:'pipi',
        sayName: function () {
            console.log(this.name)
        }
    }
    // 通过Object.definePeroperty()修改对象属性的数据属性
    Object.defineProperty(obj,'name',{
        writable:false,
        configurable: true
    })
    obj.name = 'kelly' // 欲修改name的值
    console.log(obj.name) // pipi,修改失败
    delete obj.name // 删除name
    console.log(obj.name) // undefined,name已经删除成功

注意:可以多次调用Object.defineProperty()对对象属性的数据属性进行设置,但是一旦设置了configurable的值为false之后,在进行设置就会报错
栗子:

 var obj = {
        name:'pipi',
        sayName: function () {
            console.log(this.name)
        }
    }
    // 通过Object.definePeroperty()修改对象属性的数据属性
    Object.defineProperty(obj,'name',{
        configurable: false
    })
    Object.defineProperty(obj,'name',{ // 报错
        writable:false,
        configurable: true
    })
    obj.name = "kelly"
    console.log(obj.name)

(2)访问器属性–10.29补上

2.创建对象方式

Object构造函数方法和对象字面量方法都可以创建对象,但是如果要创建多个对象,就会造成使用同一个接口创建多个对象,从而导致代码的重复书写。未解决这个问题,产生了多种更好的创建对象的方式:
(1)工厂模式:
将创建对象的过程封装在一个函数中;
在函数内部显式创建一个对象;
创建的对象属性值依赖于传进来的参数的值;
创建好对象后return出去即可;
封装好创建对象的函数之后,每次创建新的对象只需要直接调用并传入作为对象属性值的函数参数即可。

 // 工厂模式——返回一个对象的函数
    function person(name,age) { 
    // 不做构造函数,因此函数名不建议使用大写字母开头
        var p = new Object()
        p.name = name
        p.age = age
        return p
    }
    var p1 = person('kelly',16)
    var p2 = person('rubby',17)
    console.log(p1) //{name: "kelly", age: 16}
    console.log(p2)//{name: "rubby", age: 17}
    console.log(p1 instanceof person) // false
	console.log(p1.constructor === person) // false

弊端:虽然解决了创建多个相似对象可以共用同一个接口的问题,但是并没有解决对象识别的问题(即怎么知道一个对象的类型),如上述例子中,p1和p2是什么类型无法得知

(2)构造函数模式
直接在构造函数内部,将属性和方法赋值给this;
属性的值可以在创建对象时通过构造函数的参数传入;
创建新的对象时只需要调用构造函数,并传入作为对象属性值的函数参数即可
栗子:

  //构造函数模式
  function Person(name,age) {
        this.name = name
        this.age = age
        this.sayHello = function () {
            console.log("hello world")
        }
    }
    var p1 = new Person('kally',17)
    var p2 = new Person('peter',19)
    console.log(p1) //{name: "kally", age: 17}
    console.log(p2) //{name: "peter", age: 19}

1)创建对象的特点:
没有显示调用Object方法、直接把属性值赋值给this、没有return语句

2)生成新的对象的过程:
a。创建一个新的对象
b。把构造函数的作用域赋值给该新的对象(指定this的指向)
c。执行构造函数中的代码——将属性和方法赋值给该新的对象
d。返回得到这个新的对象

3)通过构造函数创建的对象可以识别其对象类型(构造函数模式优于工厂模式的地方)
通过构造函数创建的实例既是object的实例,又是构造函数本身的实例

function Person(name,age) {
        this.name = name
        this.age = age
        this.sayHello = function () {
            console.log("hello world")
        }
    }
    var p1 = new Person('kally',17)
    var p2 = new Person('peter',19)
    console.log(p1) //{name: "kally", age: 17}
    console.log(p2) //{name: "peter", age: 19}
    console.log(p1 instanceof Object)//true
    console.log(p1 instanceof Person) //true
    console.log(p2 instanceof Object)//true
    console.log(p2 instanceof Person) //true

4)构造函数的特殊用法和普通用法
构造函数也是函数,只要一个函数它是通过new来调用的就是构造函数,否则它就是普通的函数
Person函数的调用三种方式:

function Person(name,age) {
        this.name = name
        this.age = age
}

a。作为构造函数调用:

var p1 = new Person('kally',17)

b。作为普通函数被调用:

 Person('pipi',18) // 此时pipi和18将会赋值给Person的name和age,并添加到全局环境中,因此可以通过window.name和window.age读到
    console.log(window.name) // pipi
    console.log(window.age) // 18

c。作为某个对象的临时调用函数:

function Person(name,age) {
    this.name = name
    this.age = age
    this.sayHello = function () {
        console.log("hello world")
    }
}
var obj = {
    name:'mary'
}
Person.call(obj) // 在obj中调用Person函数
obj.sayHello() // hello world

5)构造函数模式的弊端:
每创建一个新的对象都会开辟新的内存来创建对象的方法,而这些方法本可以共用的,所以因为没有复用代码而造成了浪费内存,通过console.log(p1.sayHello === p2.sayHello) // false可知,
通过同一个构造函数创建的两个实例,他们的对象方法是相互独立的,即指向不同的两块内存空间。

function Person(name,age) {
    this.name = name
    this.age = age
    this.sayHello = function () {
        console.log("hello" + " "+this.name)
    }
}
var p1 =new Person('pipi',17)
var p2 =new Person('xiaohua',14)
p1.sayHello()  // hello pipi
p2.sayHello()  // hello xiaohua
console.log(p1.sayHello === p2.sayHello)  // false

那么,把构造函数中的函数提到全局中,然后创建对象时直接共用是否可行呢?
答案是否定的。sayHello作为全局函数,在调用构造函数时可以访问它,console.log(p1.sayHello === p2.sayHello) // true也证明创建的两个对象共用了同一个函数。
但是,sayHello作为全局函数,仅仅被创建某个对象时才会被调用,而它的内存是整个执行过程都不会被释放的,这又违背了没有达到封装函数和浪费内存的初衷。
因此这样的解决方式是不可行的。所以有了更好的创建对象 的方法——原型模式,请看往下看,哈哈哈

 function sayHello() {
        console.log("hello" + " "+this.name)
    }
    function Person(name,age) {
        this.name = name
        this.age = age
        this.sayHello = sayHello
    }
    var p1 =new Person('pipi',17)
    var p2 =new Person('xiaohua',14)
    p1.sayHello() // hello pipi
    p2.sayHello() // hello xiaohua
    console.log(p1.sayHello === p2.sayHello) // true

(3)原型模式

//原型模式
function Person() {

}

Person.prototype = {
    constructor:Person,
    this.name:'瓜皮',
    this.age:'18'
    this.shirt:['burberry','neilbarrett','fendi']
}

var person1 = new Person();
var person2 = new Person();

person1.shirt.push('gvc'); // ——1
person1.shirt //['burberry','neilbarrett','fendi','gvc'] //——2
p1.phone = 123 // ——3
console.log(p1.phone) // 123——4
console.log(p2.phone) //undefined // ——5

console.log(p1.hasOwnProperty("phone")) //true——6
console.log(p2.hasOwnProperty("phone")) //false——7

for(i in p1){ // ——8
  console.log(i)
} // phone name friends

for(i in p2){ //——9
  console.log(i)
}  // name friends

(1)从1~3可以得知,prototype中所有属性是所有实例共享的,也就是所有实例都指向同一块内存,对其中一个实例的共享属性做操作时,另一个实例也会受到影响!
4~5证明,如果对其中的实例操作的不是从ptototype共享而来的属性时,仅会对自己产生影响。
(2)几个方法
hasOwnProperty查找实例中的属性——6,7
for-in查找实例与原形上的属性 ——8,9
es6中的for of只会遍历实例上的属性

(4)组合构造函数和原型模式(最常见的创建对象方式)

//组合构造函数和原型模式
function Person (name,age) {
    this.name = name;
    this.age = age;
}
Person.prototype.sayName = function () {
    console.log(this.name)
}

好处:每个实例都有自己的一份独立的属性,但是又拥有共享的属性,最大限度的节省了内存

(5)动态原型模式 (先检查构造函数中的某个方法是有有效,如果无效才会在原型中动态添加这个方法,否则不添加)

// 动态原型模式
function Person (name,age) {
    this.name = name;
    this.age = age;
    if(typeof this.sayName !='function') {
        Person.prototype.sayName = function () {
            console.log(this.name);
        }
    }
} 

只有在sayName()方法不存在的情况下,才对原型添加这个方法。
(6)寄生构造函数

// 寄生构造函数模式
function Person(name,age) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.sayName = function () {
        console.log(this.name)
    }
    return o;
}
var p1 = new Person(‘lily’,13)  //与工厂模式的区别:寄生构造函数创建实

(1)与工厂模式的区别:
要用new关键字来创建,而工厂模式则是直接调用函数来创建对象
跟工厂方法没什么大的区别,所以无法检测对象类型

(2)与构造函数模式的区别:
使用构造函数模式,构造函数内部不会显示的创建对象,直接把属性赋值给this;此外,构造函数模式也没有return语句。

(7)稳妥构造函数

// 稳妥构造函数模式
function Person (name) {
    var o = new Object();
    o.sayName = function () {
        console.log(name)
    }
    return o
}

除了sayName外没有任何方式能访问Person内的数据,能保证数据的安全性。

**

3.继承

**

(1)原型链继承:

function SuperType() {
    this.name = "super"
}

SuperType.prototype.getSuperName = function() {
    return this.name
}

function SubType() {
    this.subName = "sub"
}
SubType.prototype = new SuperType();
// SubType.prototype.constructor =SubType // 把SubType的实例指向的构造函数指向改回来
SubType.prototype.getSubName = function () {
    return this.subName
}

var instance = new SubType()

console.log(instance.getSuperName()) // super
console.log(instance.constructor) // SuperType

(1)其中,作为SubType 的实例 instance且instance.proto => SubType.prototype,又因为SubType.prototype作为SuperType的实例,SubType.prototype.proto => SuperType.prototype.
(2)此时的instance.proto.constructor => SuperType(),因为在SubType.prototype = new SuperType();时,SubType.prototype被重写了。
(3) 内存图:
instance{subName:‘sub’,proto:{getSubName:getSubName,name:‘super’,proto:SuperType.prototype}}

原型链继承的坏处:原型链上的引用属性遭到修改时,会影响其他实例的该属性(如果实例上没有这个属性名的话)(因为所有的SubType的实例的_proto_都指向SuperType的同一个实例)

辅例:
// 原型链继承
// 父类型

function Super(){
   this.superProp = 'super property'
}
Super.prototype.showSuperProp= function(){  // Super原型上的方法是给实例使用的
console.log(this.superProp)
}
// 子类型
function Sub(){
  this.subProp = 'sub property'
}
Sub.prototype.showSubProp= function(){
   console.log(this.subProp)
}
//到这里还没有继承的内容,接下来要实现继承:Sub可以调用Super中的方法showSuperProp,从而打印出super property

Sub.prototype = new Super() // 关键步骤
// 子类型想要访问到父类型原型上的方法,必须是子类型的原型对象变成父类型的一个实例对象:因为父类型原型上的方法是给它的实例使用的,子类型原型上的方法也是给它的实例使用,
// 也就是说sub作为Sub的实例,它只能访问到Sub原型上的方法,而Super原型上的方法只给他的实例使用,
// 那么sub想要访问Super原型上的方法,只有让Sub的原型成为Super的实例,即Sub.prototype = new Super() 

var sub = new Sub()
// sub.showSuperProp() 直接调用没有“关键步骤
”必然会报错,sub是Sub的实例,Sub中并没有showSuperProp方法
 // 子类型想要访问到父类型原型上的方法,必须是子类型的原型对象变成父类型的一个实例对象
sub.showSuperProp() //super property

(2)借用构造函数

思路:
a。定义父类型构造函数
b。定义子类型构造函数
c。在子类型构造函数中,用call去调用父类型构造函数
关键点:子类型构造函数中调用父类型构造函数!通过call来调用!call参数中的this指向的是当前的实例,不是子类型构造函数!

function SuperType(superName) {
    this.superName = superName
    this.jackets=['armani','fendi'];
}

function SubType(subName) {
    SuperType.call(this) //把SubType的实例指给了SuperType,this代表当前实例
}

var instance1 = new SubType();
instance1.jackets.push('prada')
instance1.jackets // ['armani','fendi','prada']

var instance2 = new SubType();
instance2.jackets // ['armani','fendi']

借用构造函数解决了引用属性的问题,每一个实例的属性都是一个单独的内存。但是又出现了属性没有复用性的问题。

辅例:

// 借用构造函数
        function Person(name,age){
            this.name = name
            this.age = age
        }
        function Student(name,age,price){
            Person.call(this,name,age) // this指的是当前的实例
            // 相当于this.Person(name,age)
            this.price = price
        }
        var stu1 = new Student('pipi',18,10000000000)
        console.log(stu1) // {name: "pipi", age: 18, price: 10000000000}

(3)组合继承

(将借用构造函数以及原型链继承组合到一起)

function SuperType(name) {
    this.name = name;
    this.jackets = ['gucci','slp'];
}

SuperType.prototype.getSuperName = function () {
    return this.name
}

function SubType() {
    this.subName = 'sub';
    SuperType.call(this,'superName')
}

SubType.prototype = new SuperType()
SubType.prototype.constructor = SuperType;
SubType.prototype.getSubName = function () {
    return this.subName
}

var instance1 = new SubType();
instance1.jackets.push('fendi')
console.log(instance1)

var instance2 = new SubType();
console.log(instance2)

优点:结合了两种组合方式,分别拥有他们的优点。

内存图:

instance1={name:'superName',subName:'sub',_proto_:SuperType} 
instance1._proto_ = {name:undefined,constructor:SuperType,jackets:['gucci','slp','fendi'],getSubName:function getSubName}
instance2._proto_ = {name:undefined,constructor:SuperType,jackets:['gucci','slp'],getSubName:function getSubName}
instance1._proto_._proto_ = {constructor:SuperType,getSuperName:function getSuperName}

缺点:从内存图中,可以看出在SubType.prototype中,name 为undefined,因为SubType.prototype作为SuperType的实例,初始化了这个参数,造成了浪费。随后的寄生组合继承可以解决这个问题。
辅栗:

 		function Person(name,age){
            this.name = name
            this.age = age
        }
		Person.prototype.setName = function (name) {
            this.name = name
        }

		function Student(name,age,price){
    		 Person.call(this,name,age) // this指的是当前的实例
     		// 相当于this.Person(name,age)
    		 this.price = price
		}
        Student.prototype = new Person() // !!!为了让Student的实例访问到Person原型上的方法,会改写Student实例的构造函数的指向
        Student.prototype.constructor = Student // 修正constructor属性
        Student.prototype.setPrice = function (price) {
            this.price = price
        }
        var stu1 = new Student('pipi',18,10000000000)
        stu1.setName('xiaomiaoni')
        stu1.setPrice('无价')
        console.log(stu1) // {name: "xiaomiaoni", age: 18, price: "无价"}

(4)原型式继承

(以某个对象作为实例的原型得以继承)

function object(o) {
    function F() {}
    F.prototype = o
    return new F()
}

var person = {
    name:"jack",
    jackets:['armani','gucci']
}

var person1 = object(person);
person1.jackets.push('fendi')
person1.name='james'
console.log(person1)

var person2 = object(person);
person2.name='kobe'
console.log(person2)

es5中Object.create()封装了这种继承方式;

Object.create = function(o) { 
        function F(){}  
        F.prototype = o;  
        return new F(); 
 };

var person1 = object(person) <=> var person1 = Object.create(person)

内存图:

person1={name:'james',_proto_:{jackets:['armani','gucci','fendi'],name:jack}}

person2={name:'kobe',_proto_:{jackets:['armani','gucci','fendi'],name:jack}}

缺点:跟原型链继承一样,_proto_上的引用类型属性会被共享,因为都来自于同一对象person。

(5)寄生式继承

(跟工厂方法非常类似)

function creatAObject(o) {
    var obj = creactObject(o)
    obj.sayName = function() {
        return this.name
    }
    return obj
}

var person = {name:'baych',jackets:["fendi",'armani']};

var instance = creatAObject(person);
instance.sayName()// baych

跟构造函数一样,属性是无法复用的。

(6)寄生组合式继承

因为在组合式继承中,SubType.prototype = new SuperType()
不但把SuperType.prototype给了SubType.prototype 并且SubType.prototype作为SuperType的实例也得到了name还有jackets。
所以在这一步时会浪费内存去存这些属性。

function handleObject(subType,superType) {
    var prototype = Object(superType.prototype);
    prototype.constructor = subType;
    subType.prototype = prototype;
}

function SuperType(name) {
    this.name = name;
    this.jackets=['armani','fendi'];
}

SuperType.prototype.sayName = function() {
    console.log(this.name)
}

function SubType(name,age) {
    SuperType.call(this,name);
    this.age = age;
}

handleObject(SubType,SuperType);

SubType.prototype.sayAge = function () {
    console.log(this.age)
}

var instance = new SubType('gyj',26)

内存图:instance={age:26,name:‘gyj’,jackets:[‘armani’,‘fendi’]}
instance.proto={sayAge:function sayAge,sayName:function sayName}

总体思路:我SubType的prototype只要你SuperType.prototype上的方法。其他的交给SuperType.call(this,name)去继承

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值