《JS高程第八章 第一部分》
面向对象编程Object
8.1 理解对象
创建对象的通常方式是创建object的一个新实例,然后再给它添加属性和方法
let person=new Object();person.name="JackMa";person.age=59;person.job="福报厂创始人";person.sayHi=function(){console.log("对于你们年轻人来说996是福报");console.log("我从没得过钱,我对钱不感兴趣。")}
8.1.1属性
属性分为 对象属性以及访问器属性
1、数据属性
数据属性包含一个保存数据值的位置,值会从这个位置读取,也会写入到这个位置。数据属性有四个特性描述它们的行为。
1、[[Configurable]]:表示属性是否可以通过delete删除并重新定义,是否可以修改它的特性,以及是否可以把它改为访问器属性。默认情况下,所有直接定义在对象上的属性的这个特性都是true
2、[[Enumerable]]:表示属性是否可以通过for-in进行循环,默认情况下,所有直接定义在对象上的属性的这个特性都是true
3、[[Writable]]:表示属性的值是否可以被修改。默认情况下,所有定义在对象上的属性的这个特性都是true
4、[[Value]]:包含属性实际的值。这就是前面提到的那个读取和写入属性值的位置,这个特性默认为undefined
要修改属性的默认特性,就必须使用Object.defineProperty()方法
类方法defineProperty()
defineProperty()会直接在对象上定义一个新属性,或者修改一个对象现有的属性,并返回此对象。
语法:defineProperty(对象,属性,描述);
let person=new Object();Object.defineProperty(person,"tel",{Configurable:true, //表示是否可以通过delete删除并重新定义Enumerbable:true, //表示是否可以通过for-in进行循环Writable:true, //表示属性的值是否可以被修改Value:13888888888 //})
2、访问器属性
访问器属性不包含数据值,相反,它们包含一个获取(getter)函数和一个设置(setter)函数,不过这两个属性都不是必需的
Configurable:表示属性是否可以通过delete删除Enumerable:表示属性是否可以通过for-in循环返回。Get:获取函数,在读取属性时调用,默认为undefinedSet:设置函数,在写入属性时调用。默认值为undefined
Sample:
let book={name:"JS高程",year_:2017,edition:1}Object.defineProperty(book,"year",{get(){return this.year_;},set(newValue){if(newValue>2017){this.year_=newValue;this.edition+=newValue-2017;}}});book.year=2019;console.log(book.edition);
8.1.2类方法defineProperties()定义多个属性
let book={};Object.defineProperties(book,{year_:{value:2017},edition:{value:1},year:{get(){return this.year_;},set(newValue){if(newValue>2017){this.year_=newValue;this.edition+=newValue-2017;}}}});
上面这段代码在book对象上定义了两个数据属性year_和edition,还有一个访问器属性year
类方法getOwnPropertyDescriptor()
方法返回制定对象上的一个自有属性对应的属性描述符,自有属性指的是直接赋予该对象的属性不需要从原型链上查找的属性
语法Object.getOwnPropertyDescriptor(obj,prop)
let obj={name:"这是一个Object",number:110}const str=Object.getOwnPropertyDecscriptor(obj,"name");console.log(str.value); //这里返回"这是一个Object"console.log(str.number); //这里返回undefined
8.1.4合并对象merge
类方法Object.assign()方法 ES6新增方法
let target={a:123,b:456}let source={b:345,c:567}obj=Object.assign(target,source);console.log(target);//返回对象{a:123,b:345,c:567}
assign实际上是浅复制,拷贝指针地址,不拷贝数据
类方法Object.is() ES6新增
判断两个对象是否为同一个值
let num=1;console.log(Object.is(num,1)); //返回truelet tr=true;console.log(Object.is(tr,num)); //返回falselet nu=+0;console.log(Object.is(nu,0)); //返回true
TIPS :在JS中,数组是伪数组,本质上是一个对象。
let a=[1,2,3];//用对象的写法是let a={"0":1,"1":2,"2",3,length:3}//a[0]和a["0"]都可以调用,a[0]这里进行了隐式转换了console.log(a[0]); //返回 1console.log(a["0"]); //返回 1console.log(a.length) //返回 3for(let i=0;i<a.lenght;i++){console.log(a[i]); //返回 1 2 3}
8.1.6 增强对象语法(语法糖)
1、属性值简写
let person={name:"JackMa"}//let name="JackMa"let person1={name}
2、可计算属性
/这里本质上就是数组赋值对象属性/
const nameKey="name";const ageKey="age";const jobKey="job";let person={};person[nameKey]="JackMa";person[ageKey]=58;person[jobKey]="福报厂创始人";console.log(person);/* 这里返回的是 {name:"JackMa",age:58,job:"福报厂创始人"}*/
记住:在JS中数组本质上是对象
C语言中,指针本质上是数组
3、简写方法名
Tip1
let person={sayHello:function(name){console.log(`Hello,${name}`);}};person.sayHello('JackMa'); //返回 Hello,JackMa
Tip2
let person={sayHello(name){console.log(`Hello,${name}`);}}person.sayHello('JackMa'); //返回 Hello,JackMa
Tip1和Tip2两个都一样的
8.1.7 对象解构 ES6新增
对象解构语法,可以在一条语句中使用嵌套数据实现一个或多个赋值操作。
简单地说,对象解构就是使用与对象匹配的结构来实现对象属性赋值
Sample 不使用对象解构
let person={name:"JackMa",age:58};let personName=person.name,personAge=person.age;console.log(personName);console.log(personAge);
Sample 使用对象解构
let person={name:'JackMa',age:58};let {name:personName,age:personAge}=person;console.log(personName); //JackMaconsole.log(personAge); //58
1、嵌套解构
解构对于引用嵌套的属性或赋值目标没有限制。为此,可以通过解构复制对象属性
let person={name:"JackMa",age:58,job:{title:"福报厂创始人"}};let personCopy={};({name:personCopy.name,age:personCopy.age,job:personCopy.job}=person)console.log(person);//返回{name:'JackMa',age:58,job:{title:'福报厂创始人'}}console.log(personCopy);//返回{name:'JackMa',age:58,job:{title:'福报厂创始人'}}//下面利用嵌套解构来匹配嵌套属性let {job:{title}}=person;console.log(title); //返回 ‘福报厂创始人’
2、部分解构
如果解构存在错误,则只返回一部分
let person={name:'JackMa',age:27};let personName,personBar,personAge;try{//person.foo是undefined所以会抛出错误({name:personName,foo:{bar:personBar},age:personAge}=person);}catch(e){console.log('error');}console.log(personName,personBar,personAge);//返回 JackMa,undefined,undefined
因为前面的personBar解构失败,所以后面的personAge也是undefined
3、函数参数上下文匹配
说成白话就是解构函数的参数arguments
let person={name:'JackMa',age:27};function printPerson(foo,{name,age},bar){console.log(arguments);console.log(name,age);}function printPerson2(foo,{name:personName,age:personAge},bar){console.log(arguments);console.log(personName,personAge);}printPerson('fst',person,'snd');//返回 ‘JackMa’ 27printPerson2('fst',person,'snd');//返回 ‘JackMa’ 27
《JS高程第八章 第二部分》
继承
继承是面向对象oop编程中讨论最多的。许多oop语言都支持两种继承方式,接口继承和实现继承。
8.3.1原型链(重点)


JS高程的解释
每一个构造函数(star构造函数)都有一个原型对象(star原型对象),原型有一个函数(constructor)指回构造函数,而实例有一个内部指针指向原型(实例.proto)。如果原型是另一个类型的实例,那就意味着这个原型有一个内部指针(原型.proto)指向另一个原型,相应的另一个原型也有一个指针(原型.constructor)指向另一个构造函数。这样就在实例和原型之间构造了一条原型链
个人更推荐MDN文档的解释
在谈到继承的时候,JavaScript只有一种结构,那就是对象。每一个对象的实例都有一个私有属性(proto)指向它的构造函数的原型(prototype).该原型对象也有一个自己的原型对象(proto),层层向上直到一个对象的原型对象为null。根据定义,null没有原型,并作为原型链的中最后一个环节。换言之proto指到null就没了(这不就是链表吗?proto作为指向下一个节点的指针,然后每个节点(prototype)有一个constructor函数指向构造函数,和一个指向下一个节点的proto,同时构造函数constructor通过prototype指回节点)
MDN解释用代码解释
let person=function(name,age){this.name=name;person.age=age;}let jackMa=new person("JackMa",55);if(jackMa._proto_===person.prototype){console.log("True,jackMa._proto_ === person.prototype");}else{console.log("False,jack._proto_!==person.prototype");}/*重点:你会神奇的发现最后返回了true,*///大致的结构//1、只有函数有prototype,实例没有jack{a:1,_proto_:person.prototype{b:2,_proto_:Object.prototype{c:3,_proto_:null;}}}console.log(jack.c);//返回3//找jack中的c找不到就会往person.prototype中找,再找不到往Object.prototype中找,这就是原型链继承
梳理一下
1、首先 jackMa.proto ===> person.prototype|person.prototype.proto ===>Object.prototype |Object.prototype._proto ==> null
2、prototype节点会自己玩自己,也就是person.prototype.constrctor==>person,person.prototype==>person.prototype
3、Function和Object两个人互玩,见图二
2、原型与继承
原型与实例的关系可以通过两种方式来确定。第一种方式是使用instanceof操作符,如果一个实例的原型链中出现过相应的构造函数,则instancof返回true,否则返回false
MDN 1、继承属性
let f=function(){this.a=1;this.b=2;}let o=new f();f.prototype.b=3;f.prototype.c=4;/*不要直接上面这样定义属性,会打破原型链*/console.log(f); //返回 {b:3,c:4}console.log(o); //返回 f.prototype {a:1,b:2}console.log(o._proto_); //返回 {b:3,c:4}console.log(o._proto_._proto_);//返回 Object.prototypeconsole.log(o._proto_.proto_._proto_); //返回nullconsole.log(f.b); //返回undefinedconsole.log(f.c); //返回4
MDN 2、继承方法
在JavaScript中,并没有其他语言那样的方法,任何函数都可以添加到对象上作为对象的属性。函数的继承与其他属性的继承没有差别,包括“属性遮蔽”相当于方法重写
let o={a:2,m(){return this.a+1;}};console.log(o.m()); //返回3let p=Object.create(o); //ES6之前的写法let p1=new o(); //ES6的写法p.a=4;console.log(p.m()); //返回5
MDN 3、在Javascript中使用原型
function doSth(){}console.log(doSth.prototype); //这里返回对象{constructor:f},constructor构造函数指向的是f doSth()//和声明函数的方式无关//JavaScript中的函数永远有一个默认原型属性var doSth=function(){};console.log(doSth); //这里返回对象{constructor:f},constuctor指向的是f()//
MDN 4、给原型对象添加属性
function doSomthing(){}doSomthing.prototype.foo="bar";doSomthing.prototype.tel=110;console.log(doSomthing.prototype); //这里返回对象doSth里面有两个属性 1、foo和2、tel
MDN 5、通过new来创建实例
function doSth(){}doSth.prototype.thing="加班";let doSthInstacne=new doSth();doSthInstance.prototype.name="JackMa";console.log(doSthInstance);//这里返回对象/*doSth{name:"JackMa",_proto_:{thing:"加班"constructor:f doSth(){}_proto_:Object}}*/
类Class
8.4.1 类定义,类实际上是语法糖
//类声明class person{}//类表达式let person=class{};
MDN: class 定义创建一个基于原型继承的具有给定名称新类
class Polygon{constructor(height,width){this.area=height*width; //这个area 实际上是属性this.calcArea=function(){return height*width;}}}let p=new Ploygon(100,100);console.log(p.area); //这里返回10000console.log(p.calcArea()); //这里返回10000
1、把类当做特殊函数
class Person{};console.log(typeof(Person)); //返回Functionconsole.log(Person._proto_===Function.prototype); //返回trueconsole.log(Person._proto_.constructor===Function); //返回true
记得上节原型链中的第二张图的Fuction吗,自己玩自己,比Object还会玩,而这个Person类实际上就是图中animal
还有更会玩的
class person{};let p=new person();console.log(p instanceof person); //返回 trueconsole.log(person instanceof Function) // 返回trueconsole.log(person instanceof Object); //返回true/****function和obje互玩/console.log(Function instanceof Object);//返回trueconsole.log(Object instanceof Function); //返回true/**同理**/console.log(Array instanceof Function); //返回什么?console.log(Array instanceof Object); //返回什么?
2、类可以进行实例化,函数也可以进行实例化
let sum=function(a,b){return a+b;}let s1=new sum();console.log(s1._proto_.constructor(10,5)); //返回15
类继承。extends,背后原理依然还是原型链
1、extends关键字用于类继承或者类表达式中,创建一个类是另一个类的子类
class ploygon{constructor(width,height){this.width=width;this.height=height;this.name="多边形";}}//square 继承polygonclass square extends ploygon{constructor(length){super(length,length);this.name="正方形";}set area(){return this.width*this.height;}get area(value){this.area=value}}let s1=new square(5);console.log(s1.area); //返回25
2、抽象基类,阻止实例化
通过new.target来实现
class Polygon{constructor(){if(new.target==Polygon){console.log("Ploygon是基类不能被实例化!");throw new Error('Polygon 是基类不能进行实例化');}}}let p=new Polygon();//这里抛出异常 Error: polygon 是基类不能进行实例化at new Polygon
3、继承内置类型
ES6类为继承内置引用类型提供了顺畅的机制,开发者方便扩展内置类型
class superArray extends Array{shuffle(){for(let i=this.length-1;i>0;i--){let j=Math.floor(Math.random()*(i+1));[this[i],this[j]]=[this[j],this[i]];//上面那段代码拆开let tmp=this[i];this[i]=this[j];this[j]=tmp;}}}supperArray s1=new superArray();console.log(s1 instanceof superArray); //返回trues1.shuffle();console.log(s1); //这里一次返回 [1, 5, 3, 9, 7, 4, 2, 8, 6]s1.shuffle();console.log(s1); //这里一次返回[5, 9, 4, 6, 7, 8, 2, 1, 3]
