javascript对象编程

function A() {

}
A.prototype.say = function(){
    console.log("hhh");
}
function B() {
    return Object.create(A.prototype);
}

const c =new B();
console.log(c);

开始之前,回顾一下自己愚蠢的错误,希望我明晰函数和对象的区别
函数是动态地执行操作
对象是对一个东西的描述
至于构造函数:动态操作去构造一个对象(赋值)
在这里插入图片描述

面向对象编程

框架的本身也是把面向过程编程 => 面向对象编程

function ChangeImg (options) {
    this.btnObj = options.btnObj;
    this.imgObj = options.imgObj;
}

ChangeImg.prototype.init = function() {
    let that = this;
    that.btnObj.onclick = function () {
        if ( this.value == '变化') {
            that.imgObj.src = '../imgs/test.png';
            this.value = '还原';
        } else {
            that.imgObj.src = '../imgs/test.png';
            this.value = '变化';
        }
    };
};

// 实例化对象
let obj = new ChangeImg ({
    btnObj: document.getElementById('btn'),
    imgObj: document.getElementById('img')
});

对象属性

属性分为:数据属性、访问器属性
访问器属性不包含数据值,通常包含getter、setter函数

采用内部特性来描述属性的特征
属性描述符是ES5开始支持的语法

  • Configurable是否可以通过delete删除、是否可以修改特性,是否更改为数据/访问器属性、默认true
var obj={
	name: 'zhagnsan',
	age: 19 
}
delete obj.name //true
typeof obj.name //undefined
  • Enumerable是否可以通过for-in循环返回,默认true
  • 【数据特有】Writable
  • 【数据特有】Value
  • 【访问器特有】Get,默认undefined
  • 【访问器特有】Set,默认undefined
get name(){
 //返回值作为属性访问表达式的值
}

就是用点属性的方式获得函数的返回值

可以理解为闭包间接访问一个变量

获取内部特性
(1)通过Object.getOwnPropertyDescriptor()方法可以获取指定属性的属性描述符(只可以获取自己的属性,即实例不能通过调用该方法知道自己的原型属性)
(2)in操作符返回,是否可以通过对象访问属性(原型、实例)

”name" in person;
person.hasOwnProperty("name");//实例上的

修改定义属性
访问器属性不能直接定义,在支持Object.defineProperty()的浏览器中可以通过其修改特性,还可以多个描述符一次性定义多个属性

let person={};
Object.defineProperty(person,"name",{
	writable:false,
	value:"hh"
	});
//定义多个属性
let book={};
Object.defineProperty(book,{
	year:{//数据属性
 		value:2017
 	},
 	edition:{//访问器属性
 	   get(){
			return this.year;
		},
	   set(newyear){
	   if(newyear>2017){
	   		this.year=newyear;
	   	}}
 	}
})

语法

assign合并对象 又称混入mixin

extend
Object.assign()接受一个目的对象和一个或者多个源对象作为参数
浅复制:复制引用
同一属性多值取最末
复制可枚举和自有属性、键为符号的属性,即Object.propertyIsEnumerable()和Object.hasOwnProperty()返回真值的属性
没有回滚,尽力而为可能只完成部分

  var obj1=new Object({id:'1'});
  var obj2=new Object({id:'2',a:'obj2',b:'ds'});
  var obj3=new Object({id:'3'});
res=Object.assign(obj1,obj2,obj3);
console.log(res);//{id: "3", a: "obj2", b: "ds"}
res.a="res define";
console.log(res);//{id: "3", a: "res define", b: "ds"}取最后的那个
console.log(obj1);//{id: "3", a: "res define", b: "ds"}浅复制
console.log(obj2);//{id: "2", a: "obj2", b: "ds"}
console.log(obj3);//{id: "3"}

Object.is(两个参数)

在比较上的粒度等同于=== ,且对一些情况进行了修正如下。

console.log([]===[]);//false
console.log(Object.is([],[]));//false
console.log({}==={});//false
console.log(Object.is({},{}));//false
console.log(-0===0&&0===+0);;//true
console.log(Object.is(+0,0));//true
console.log(Object.is(+0,-0));//false
console.log(Object.is(-0,0));//false
console.log(NaN===NaN);//false
console.log(isNaN(NaN));//true
console.log(Object.is(NaN,NaN));//true

可计算属性与简写

[varName](args){//中括号代表:运行时视作JavaScript表达式
    以变量varName的值命名的函数;
}
functionName(argu){}
let person={
	//name:name
	name
}

对象解构

将对象的属性值赋值给对应结构
嵌套解构
部分解构
参数上下文匹配解构

//嵌套解构
//声明title变量并将person.job.title赋值给它
let {job:{title}}=person;
//区别于上面{} p.pName赋值给mName
let {pName:mName,pAge:mAge}=p;
//区别于上面同名匹配,p.pName赋值给pName
let {pName,pAge='没有就会使用这个作为默认值’}=p;
//参数上下文匹配解构
function print(foo,{name,age},bar){}
print("hh",person,"dd");

创建对象

工厂模式

适用于创建多个类似的对象实例,不能对对象做出标识。

function createPerson(name,age){
    let o=new Object();//显式创建对象
    o.name=name;
    o.age=age;
    o.sayName=function(){
        console.log(this.name);
    };
    return o;
}
let p1=createPerson("Greg",27);

对比工厂模式和构造函数,工厂内部生产出一个完整的对象并定义属性;构造函数将new提到了函数之外,承担了一个黏合属性和对象的作用。

应用:
实际编程中,设置一个数据默认值,可能会被很多地方引用,为了防止有人修改,可以采用工厂模式函数来产生数据默认值并返回给每一个需要方。
vue里面的data是一个工厂模式的函数:vue中组件是用来复用的,当某一处复用的地方组件内data数据被改变时,其他复用地方组件的data数据不受影响,就需要通过data函数返回一个对象作为组件的状态。

构造函数模式

构造函数与普通函数的区别:调用方式

function Sup(){
    this.colors=["red","blue"];
}
Sup.prototype=new Array();

function Sub(){
    //继承super
    //Sup.call(this);//在子类构造器中,调用父类构造函数。还可以传参数
    return new Sup();
}
console.log(Sup.prototype);
console.log(Sub.prototype);
let i=new Sub();
let si=new Sup();
console.log(i);
console.log(si);

理解了这个输出,就能理解new的过程了:
在这里插入图片描述

new操作符调用构造函数:

  1. 在内存中创建一个新对象
  2. 新对象的prototype特性被赋值为构造函数的prototype属性
  3. 构造函数中的this指向新对象
  4. 执行构造函数的代码,为新对象添加属性
  5. 如果构造函数没有返回对象,就返回新对象

prototype属性与其他属性地区别,用prototype编写一个类后,如果new一个新的对象,浏览器会自动把prototype中的内容替你附加在对象上。通过利用prototype就可以在JavaScript中实现成员函数的定义,甚至是“继承”的效果。

没有使用new操作符调用函数,即普通函数调用。会将属性和方法添加到当时的执行上下文,取决于构造器内的this指向


/除了声明函数也可以写成表达式
//Let Person=function (name,age){
  function Person(name,age){
    //let o=new Object();
    this.name=name;
    this.age=age;
    this.sayName=function(){
        console.log(this.name);
    };
    //return o;
}
Person.prototype=[];
let p1=new Person("Greg",27);
console.log(Person.prototype);
console.log(p1.prototype);
console.log(p1);


构造器模式会继承原型!但是注意实例原型属性名不一定,直接打印出来看看:
盗用构造器不会,因为只有在new调用的时候才会自动继承prototype属性,而盗用构造器是使用apply方法借用构造器
在这里插入图片描述

分析构造模式,发现每个对象都有自己的一个sayName方法,很浪费,如果把方法定义在构造器之外,又会导致作用域混乱。所以出现了原型模式(把方法定义在构造器之外同时加上类的标签ClassName.prototype不污染作用域,即定义一个类属性对象来保管共享作用域)。

原型模式

可以通过实例调用原型,但是不能通过实例修改原型
是实现所有原生引用类型的模式,原型模式的实现关键,是语言本身是否提供了clone方法

实例类可能都需要一些相同的方法和属性,各自拥有太浪费,所以统一指向prototype

Object.getPrototypeOf、isProtypeOf都是根据属性[[prototype]]来判断和索引的

Object.getPrototypeOf(实例名)//返回参数内部特性[[prototype]]原型对象的值,不是所有对象都暴露[[prototype]]属性

有关prototype可以查看这篇

原型语法

区别重写原型对象修改新增原型对象属性
重写对象,改变了引用,只有在重写后创建的实例才会引用新的原型,即采用对象字面量的方式。注意对constructor的影响(constructor属性会被改写指向Object)。
原型的动态性:随时修改新增原型对象属性,能够立即反应在所有对象实例上。适用于共享

通过Object.setPrototypeOf(原型对象,addition);可以修改原型对象的属性,但是不提倡这样直接修改。最好是先通过Object.create(原型对象);创建一个新的对象

function object(o){//object.create的单参数
    function F(){}
    F.prototype=o;//
    return new F();//传入一个原型,返回一个根据原型创建的对象实例
}

create与new 的关键区别是声明后系统对资源的控制权不一样。
create是在内存中重新创建一个完全独立的新的资源实例,并且不受GC的资源回收控制,必须自己回收。即使内存中存在一个这样的实例,编译器也重新创建一个。
new声明的时候,编译器回自动寻找内存中的相应实例,找到了以后,将资源计数器+1,并不会在内存中重新声明内存地址空间,他是受GC限制的,当编译器运行完毕后,会自动将资源编译器里面的该对象的资源计数器-1,如果为计数器0的话就自动释放该对象。
Garbage Collection

原型层级

再通过对象访问属性,会按照属性名从实例本身开始搜索,如果没有找到就会沿着指针进入原型对象。

通过在实例上添加同名属性的方法可以屏蔽原型属性,注意如果原型链上这个属性是只读,那么不能赋值,因为属性赋值都会查询原型链
设置属性不能影响原型链是JavaScript很重要的属性。调用原型链上的访问器方法,方法对实例进行操作,方法生成的属性在实例上

实例名.hasOwnPrototype("属性名“);//检测属性是否属于实例
”属性名“ in 实例名;//是否可以访问属性
属性 !==undefined

注意: 属性 !==undefined 不能区别undefined和null,但是in可以,对一个存在但是值为undefined的值 使用操作符in,返回true

Person.prototype.constructor===Person;//true
p1.__proto__ ===Person.prototype

遍历对象属性

for-in循环//通过对象可以访问的可枚举的属性名称
Object.keys(对象名);//可枚举的实例属性名称
Object.getOwnPrototypeNames(对象名);//包括不可枚举的实例属性名称
Object.getOwnPrototypeNames-Symbol(对象名);//符号为键的属性

因为符号为键的属性没有名称的概念

对象迭代

Object.values()、Object.entries()两个静态方法接收一个对象,返回他们内容的数组

继承

原型链继承

原型对象不采用构造函数默认的原型,而是另一个类的实例

SubType.prototype=new SuperType();
最根处是Object.prototype
SubType的原型被赋值为父类的实例,在SubType.prototype上的修改和定义被SubType实例共享,不影响父类的实例。

instanceOf操作符和isPrototype()方法都是只要原型链中包含即可

原型链弊端

  • 原型中包含的引用值在所有实例键共享(所以一般属性定义在构造函数)
  • 子类型在实例化时,不能给父类型的构造函数传参(无法在不影响所有实例的情况下

所以提出了盗用构造函数(对象伪装/经典继承)

盗用构造函数(对象伪装/经典继承)继承

在子类构造函数中调用父类构造函数。解决了单独传参问题
注意盗用的是父类的构造器,与父类的prototype对象无关

function Sup(){
    this.colors=["red","blue"];
}

function Sub(){
    //继承super
    Sup.call(this);//在子类构造器中,调用父类构造函数。还可以传参数
}

let i=new Sub();
i.colors.push("black");
console.log(i.colors);//red","blue","black"
let j=new Sub();
console.log(j.colors);//red","blue"

可以传递参数,相当于在子类构造器上执行父类构造器的代码,缺点同构造函数模式,浪费不能共享。此外相比构造器模式,盗用构造函数的子类还不能访问父类原型上定义的方法。
想要实现继承原型需要

Writestream.prototype = Writeable.prototype;

组合继承(伪经典继承)

使用原型链继承原型上的属性和方法,通过盗用构造函数继承实例属性。

functiion Super(){
    this.name=name;//各自的属性,构造函数继承
    this.colors=["red","blue"];
}

Super.prototype.sayName=function(){
    console.log(this.name);
};//原型链上的方法共享

function Sub(name,age){
    //继承super
    Super.call(this,name);//在子类构造器中,调用父类构造函数
    this.age=age;//新增属性
}

Sub.prototype=new Super();//原型,继承方法
Sub.prototype.sayAge=function(){
    console.log(this.age);
};//原型链上的方法共享,新增
class Sub extend Super{
//使用关键字super即可调用父类构造器
}

寄生虫模式

相比原型模式,将父类实例赋值给子类原型,寄生虫直接用父类原型创建一个原型对象。
以下给出寄生虫组合模式代码(最有效
两个参数:子类构造器和父类构造器
相比组合继承模式,寄生虫组合继承模式只调用了一次父类构造器。

function inheritPrototype(subT,superT){
    let prototype=Object.create(superT.prototype);//创建父类原型的副本对象给子类的prototype
    prototype.constructor=subT;//绑定
    subT.prototype=prototype;//绑定
}

补充object.create

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。

Object.create(proto,[propertiesObject])
propertiesObject
可选,原始包装对象。如果该参数被指定且不为 undefined,该传入对象的自有可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)将为新创建的对象添加指定的属性值和对应的属性描述符。
属性在原型下,但是可以直接调用,实质是通过原型链proto来访问属性

区别点

  • new Object() 、构造函数来创建对象, 添加的属性是在自身实例下。如下实例:相比构造函数继承原型+内部属性,create实现只继承people原型
//创建一个构造函数或者类
var People = function(){
  this.gouzao="不在原型上"
}
People.prototype.y = 20
People.prototype.showNum = function() {}
//通过构造函数创建实例
var p = new People();
console.log(p.__proto__ === People.prototype) // true
console.log(p);
var createp = Object.create(People.prototype);
console.log(createp.__proto__ === People.prototype) // true
console.log(createp);
var createpp = Object.create(new People());//?到底是否实现挂载People在原型下面
console.log(createpp.__proto__ === People) // false类型不同
console.log(createpp.gouzao);//可以访问
console.log(createpp);
  • Object.create() 用第二个参数来创建非空对象的属性描述符默认是为false的,而构造函数或字面量方法创建的对象属性的描述符默认为true。Object.getOwnPropertyDescriptors
  • 空对象的生成:构造函数、对象字面量方法创建空对象时,对象有原型属性的;而Object.create() 创建空对象是没有原型属性的。

注意:运行环境不一定要部署原型属性,因此不建议直接使用属性操作原型,建议使用Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。
在这里插入图片描述

前文围绕JavaScript模拟class-like的行为,ES6中引入class(背后的实质仍然是原型和构造函数)
类的实质:立即执行函数在框定一个命名空间,所以类定义都不能提升
立即执行函数的返回值是构造函数

console.log((typeof (new (class{ class(){}})))); // object new产生一个对象
console.log((typeof (class{ class(){}}))); // function
console.log(typeof ({class(){}}));  // object
//  console.log(typeof (class(){}));  // object
console.log(typeof (class{}));  // function

在这里插入图片描述
在这里插入图片描述

与定义函数一样,定义类有两种方式。但是区别在函数声明可以提升,但是类定义都不能提升
函数受函数作用域限制,类受块作用域限制。(var/let)

最主要是因为 即使没有出现“use strict”指令,class中所有代码也默认处于严格模式

//类声明
class P{};
//类表达式
const P=class {};


//立即实例化一个类
let p=new class P{
	constructor(x){
		console.log(x);
	}
}('bar');

constructor用于在类定义块内部创建构造函数,constructor告诉解释器在使用new创建类实例时调用这个函数。
构造函数可以不用new调用(以全局的this作为内部对象通常是window)不同的是,类构造器必须要用new调用(类内部通过constructor函数实例化)

p();//报错


补充
注意:类内部定义的function在原型上,但是变量在实例
不是这样的
ES6标准的类,只允许创建方法(获取方法、设置方法、生成器)和静态方法,还没有定义字段的语法。如果想要在类实例上定义字段(实例属性),必须在方法中进行。静态字段,必须在类体之外,在定义类之后定义。
但是扩展类语法在发展,支持定义实例和静态字段的标准化在发展。
浏览器逐渐支持定义公有实例字段,且定义公有实例字段的语法在使用React和Babel转义的JavaScript程序中非常常用。

class B{
 	constructor(){
 		this.name='k';
 	}
}
//定义公有实例字段
class B{
 	name='k';
}

下一步,变量名前缀#,表示变量私有,只能在类体中使用,对类体外部的任何代码都不可见

实例成员&原型方法&静态类方法

方法 可以 不可以
实例方法 不可以 可以
原型方法 不可以 可以

能否被构造函数调用实例化对象调用类调用
静态可以不可以可以
实例不可以可以不可以
原型不可以可以不可以

注意:静态方法和原型对象一样是作为构造函数的属性 来定义的
注意区别方法和变量,构造函数虽然不能调用实例方法,但是可以使用实例变量

类方法即原型方法:等同于对象属性,也可以使用字符串、符号或者计算的值作为键

class P{
    constructor(){
        //添加到this上的全部都会成为实例成员
        this.locate=()=>console.log('实例');

    }

    //在类块中定义的所有内容都会定义在类的原型上
    locate(){
        console.log("原型");
    }
     //静态,定义在类本身上
     static locate(){
        console.log("class",this);
    }
}

let p=new P();
p.locate();//实例
p.prototype.locate();//原型p.__proto__ .locate();//原型
P.locate();//class,class P{}

有些浏览器没有支持prototype,而是通过__ proto __ 属性提供支持

静态方法:用于执行不特定于实例的操作,与原型成员类似,每个类上只有一个。
调用静态方法不需要实例化该类,但不能通过一个类实例调用静态方法。静态方法通常用于为一个应用程序创建工具函数。
在静态成员中,this引用类本身。
注意:即使共享,静态方法是作为构造函数而非原型对象的属性定义的

非函数原型和类成员

类定义没有显式支持在原型上或者类上添加成员数据,但在类定义外部可以手动添加

不支持是因为在共享目标(类和原型)上添加可修改的数据成员是一种反模式。一般来说,对象实例应该独自拥有通过this引用的数据。

//static greeting=="hello";不支持
P.greeting="hello";
//在原型上定义数据成员
P.prototype.name='xx';

举个复数类的例子

class Complex{
  //声明私有字段来保存
  r=0;
  i=0;
  //构造函数定义在每个实例上创建的实例属性
  constructor(real,imaginary){
    this.r=real;
    this.i=imaginary;
  }
  //复数的加减计算方法,定义在原型上
  plus(that){
    return new Complex(this.r+that.r,this.i+that.i);
  }
}

//为已有类添加一个计算共轭复数的方法
Complex.prototype.conj=function (){
  return new Complex(this.r,-this.i);
}

类对生成器和迭代器的支持

class P{
    //在原型上定义生成器方法
    *createNameIterator(){
        yield 'n1';
        yield 'n2';
    }
    //也可以在类上定义 static *name
}//

let iter=new P().createNameIterator();
console.log(iter.next().value);//n1
console.log(iter.next().value);//n2

通过添加Symbol.iterator属性可以把类变成可迭代对象。

 *[Symbol.iterator](){
                    yield 1;
                }

ES6继承语法

ES6 支持单继承,使用extends可以继承任何拥有constructor和原型的对象。这意味着可以继承一个类,也可继承普通的构造函数

class Bus extends Vehicle{};
let Bus=class extends Vehicle{};

派生类通过原型链访问类和原型上的定义的方法,体会this的值,反映调用相应方法的实例或者类


class SuperT{
    identify(id){
        console.log(id,this);//this的值反映调用相应方法的实例或者类
    }
    static identifyClass(id){
        console.log(id,this);//this的值反映调用相应方法的实例或者类
    }
}
//继承
class  SubT extends SuperT{}

let son=new SubT();
let father=new SuperT();

son.identify('son');//son SubT{}
father.identify('father');//father SuperT{}
SuperT.identifyClass('父类');//父类 class SuperT{}
SubT.identifyClass("子类");//子类 class SubT{}


派生类的方法通过super引用原型

class Bus extends Vehicle{
    constructor(){
       //调用父类构造器
        super();
        //this=返回实例
         //必须在这之后才能引用this,否则抛出ReferenceError
         
    }
};

super使用注意

  1. 只能在派生类的构造函数静态方法中使用
  2. 在静态方法中使用super调用父类的静态方法
  3. 不能单独引用关键字,用么调用构造函数();要么引用静态方法
  4. 构造函数,只有先调用super(),才可以使用this
  5. 如果在派生类中显示定义了构造器,要么必须调用super(),要么返回一个对象

抽象基类:供其它类继承extends,而本身不会被实例化new
在构造函数中使用new.target来实现,new.target保存通过new调用的函数或者类

new.target并非一直引用它所在的构造函数,也可能引用子类的构造函数

class Shape {
  constructor() {
    if (new.target === Shape) {
      throw new Error('本类不能实例化');
    }
  }
}
 
class Rectangle extends Shape {
  constructor(length, width) {
    super();
    // ...
  }
}
 
var x = new Shape();  // 报错
var y = new Rectangle(3, 4);  // 正确

另外抽象基类还可以要求派生类必须定义某个方法,也是通过在抽象基类构造函数中检查this

if(!this.foo){
    throw new Error("继承我必须定义foo方法");//在定义之前,实例化派生类都会抛出这个错误
}

ES6类为继承内置类型提供了顺畅的机制,开发者可以方便扩展。

类混入

ES6没有显式提供**多类继承 **,但是可以通过嵌套继承模拟

class Vehicle{}
function getParent(){
    return Vehicle;//返回类
}
class Bus extend getParent() {}
class V{}
let F=(Superclass)=>class extends Superclass{
    F(){
        console.log("F");
    }
}

let B=(Superclass)=>class extends Superclass{
    B(){
        console.log("B");
    }
}

class S  extends F(B(V)){}
 let s=new S();
 s.F();//F
 s.B();//B

组合胜过继承,很多JavaScript框架已经抛弃混入模式,转向组合模式。把方法提取到独立的类和辅助对象中,然后把他们组合起来

Decorator 类的修饰器

(Decorator)是一个是编译时执行的函数,用来修饰类的行为,修饰器对类的行为的改变是在代码编译时发生的,而不是在运行时。
通俗的说:给类添加或者修改类的变量与方法
对于类来说,这项功能将是 JavaScript 代码静态分析的重要工具
只能用于类,而不能用于函数(因为函数存在提升,而类不会提升)

组合委托而不是继承

对象的一些方法

toString

每当需要把一个对象转换为字符串的时候,JavaScript就会调用对象的这个方法,原生object原型上实现的并没有透露太多信息,推荐重写覆盖

valueOf

每当需要把一个对象转换为某些**非字符串原始值(通常是数值)**的时候,JavaScript就会调用对象的这个方法。
如果在需要原始值的上下文中使用了对象,就会调用他的valueOf

toJSON

Object.prototype上没有这个方法,但是JSON.stringify方法会从要序列化的对象上寻找这个方法

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值