1.理解原型
1、复用代码 <=> 继承 <=> 原型
复用代码的其中一种方式是继承,在js中继承的实现是通过原型。
2、判断对象是否存在某个属性—— in 操作符
const a ={
age:1
}
console.log('aAtt' in a) // true 属性名前加引号!
3、为对象添加原型对象-Object.setPrototypeOf(obj1,obj2)
const a = {
age:1
}
const b = {
name:2
}
// 将b设为a的原型,在a找不到的属性会去原型b找,通过原型链向上查找
Object.setPrototypeOf(a,b) //每个对象都有一个原型,每个对象都原型都可以有一个原型,形成原型链
console.log('name' in a) //true 对象可以直接调用原型的属性 如同调用自己的属性一样 .方式
// {age: 1}
// age: 1
// __proto__:
// name: 2
// __proto__:
// constructor
4、构造器与原型
每个函数都有一个原型对象 => prototype ,该原型对象是函数实例化后的对象的原型
这个原型对象默认只有一个contructor属性,该属性指向该函数本身
实例化后的对象的原型是构造函数的原型对象,而实例化后的对象可以直接调用原型的属性,所以,constructor是构造函数本身
function aa(){
this.name="wang",
this.age=11
return 1;
}
aa.prototype.action=function (){ //函数的原型对象是prototype
return this.name //原型内方法可以使用this,this为实例化后的对象
}
console.log('aa.prototype',aa.prototype) //{ action:function(){},constructor:aa函数体 }
console.log('aa.prototype.constructor',aa.prototype.constructor) // aa函数体
const obj=new aa()
obj.action(); // 实例化后的对象的原型对象没有名称prototype,是可以直接访问原型对象的
obj.constructor // aa函数体
obj
// {name: "wang", age: 11}
// age: 11
// name: "wang"
// __proto__:
// action: ƒ ()
// add: "青岛"
// constructor: ƒ aa()
// __proto__: Object
当构造器函数的方法与原型方法名称重合时,优先使用构造函数内方法。但是不推崇
注意:原型方法只能在实例化后的对象中使用,普通调用函数赋值给对象,使用不了原型方法。
通过构造函数实例化大量对象时,重复拷贝意义不大,且会消耗大量内存。冲这个角度看,在函数原型上创建对象方法时很有意义的,可以使同一个方法由所有对象实例共享
5、js动态特性的副作用
函数由默认原型对象,没有显示的修改原型对象,则只有constructor属性,指向该函数本身。
在实例化对象前后修改原型对象都可以,但给原型对象重新整体赋值
,则需要重新实例化对象。
之前实例化的对象保留着对原来的原型对象的引用,可以继续访问。
现在实例化的对象只能访问重新赋值后的原型。
因为实例化对象时,对象原型与函数原型建立引用关系,所以实例化前后修改都可以访问;重写函数原型时,需要重新实例化对象,建立新的引用,否则愿对象只能访问原来的引用。
function aa(){
this.name="wang",
this.age=11
return 1;
}
aa.prototype.action=function (){
return this.name
}
const obj=new aa()
aa.prototype.add="青岛";
console.log(obj) //
//aa {name: "wang", age: 11}
//age: 11
//name: "wang"
//__proto__:
//action: ƒ ()
//add: "青岛"
aa.prototype={
add:"安徽"
}
const obj1=new aa()
console.log(obj1)
//aa {name: "wang", age: 11}
//age: 11
//name: "wang"
//__proto__:
//add: "安徽"
- 函数有prototype对象
- constructor:指向自己(默认只有该属性)
- 原型属性(手动添加才有)
- 原型方法(手动添加才有)
- 实例化对象有proto对象,直接调用原型属性和方法
- obj.name(对象没有会从原型链中查找)
- obj.constructor (functioin)
6、通过构造函数实现对象类型 instanceof
instanceof:检测一个实例是否由特定构造函数创建。 可以由多个构造函数创建出来 可能是直属构造函数创建,也可能是由构造函数的原型创建出来。
prototype 属性的值有可能会改变,改变之后为false
constructor:属性是创建实例对象函数的引用。它的存在仅仅是说明该对象是从哪创建出来的,如果该值被重写,原始值就丢了
7、实现继承
将一个对象的实例赋给另一个对象的原型。
A.prototype = new B()
注意:A.prototype中的constructor被改写了,查找对象的constructor找不到就会去原型B对象找,找不到去B的原型找,最后找到了函数B。其实是不对的
const a=new A()
a instanceof A //true instanceof判断函数是否继承原型链上的对象
a instanceof A //true
a.constructor = functio B
这种方式继承的好处是继承函数的原型将实时更新。原型改了,对象中访问的原型属性可随之更改。或者可以选择重新改写原型对象。
8、重写constructor的问题
js配置属性功能 —— Object.defineProperty方法
- configurable 是否可以修改、删除属性
- enumerable 是否可枚举,为true时可以在for in循环中出现
- value 属性值,默认undefined
- writable 是否可以通过赋值语句修改属性值,可写
- get 定义getter函数,当访问属性时发生调用,不能与value和writable同时使用
- set 定义setter函数,当对属性赋值时发生调用,不能与value和writable同时使用
- Object.defineProperty(对象,'属性名',{配置项内容})
var nin = {}
nin.name = 'yo'
nin.age = 11
Object.defineProperty(nin,'sne',{
configurable:false, //不允许修改 删除
enumerable:false, //不可枚举
value:true, //值是true
writable:true//可写
})
//解决constructor问题
function Person(){
this.age='per'
}
Person.prototype.dance=function(){
}
function Nin(){
this.name='nin'
}
Nin.prototype = new Person() //继承
Object.defineProperty(Nin.prototype,'constructor',{
enumerable:false, //不可枚举
value:Nin, //Ninja函数
writable:true //可写
})
const nin = new Nin()
9、 instanceof操作符
nin instanceof Nin
操作符instanceof用于检测【Nin函数原型】是否出现在实例【nin的原型链中】。
10、当心构造函数的改变
function Nin(){}
const nin = new Nin()
nin instanceof Nin //true
Nin.prototype = {} //重置原型对象,需要重新实例化对象才能获得新原型的引用
nin instanceof Nin //false Nin原始重置了,nin仍然保存之前原型的引用,或者这样理解:Nin原型是{},但是nin的原型是Nin 默认原型,所以不匹配为false
11、class
在js中使用关键字class,底层实现仍然是基于原型继承。
创建class类
class Nin{
// 构造函数
constructor(name){
this.name=name // 这是给实例初始化的
a = 1 // 注意!!这里定义的不是在prototype上的属性,而是给实例初始化的
}
//原型方法
swing(){
return true;
}
}
var ninja = new Nin('wang') //new 调用类
class语法糖转为es5
//以上转为es5
function Nin(name){
this.name = name;
}
Nin.prototype.swing = function(){
return true;
}
var ninja = new Nin('wang') //new 构造函数
静态方法
class Nin{
// 构造函数
constructor(name){
this.name=name
}
//原型方法
swing(){
return true;
}
static compare(nin1,nin2){
return nin1.name + nin2.name
}
}
var ninja = new Nin('wang') //new 调用类
var ninja1 = new Nin('liu')
ninja.compare //undefined
Nin.compare(ninja,ninja1) //wnagliu
//静态方法是类方法,传入两个实例化对象
静态方法转为es5
function Nin(name){
this.name = name;
}
Nin.compare = function(ninja,ninja1){...}
//函数是第一类对象,所以也有属性,静态方法就是给构造函数添加属性方法
实现继承
es5继承,对所有实例均可访问对方法必须直接添加到构造函数原型上。
将父函数的实例化对象赋给子函数的原型。
但是这会修改constructor属性,所以需要Object.defineProperty方法手动重置constructor属性。
es6继承
//父类
class Nin{
constructor(name){
this.name=name
}
swing(){
return true;
}
static compare(nin1,nin2){
return nin1.name + nin2.name
}
}
//子类
class Son extends Nin{ //extends继承
constructor(name,wea){
super(name);
this.wea = wea;
}
weapon(){
return true+super.swing();
}
}
var ninja=new Nin('wnag');
//父类
class Person{
constructor(name){
this.name = name
}
age:'1',//原型属性
showName(){
console.log(`父类名字:${this.name}`) //原型内能访问this对象
}
}
//子类
class Student extends Person{
constructor(name,skill){
super(name) //子类构造函数必须先调用super父类构造函数
this.skill = skill //子类自己构造函数其他属性
}
//子类原型方法与父类原型方法重名时,子类方法优先
showName(){
super.showName();//父级的方法执行,此处继承了父类方法
console.log(`子类名字:${this.name}`)//并且添加了自己的东西
//若隐藏掉super,则完全是自己的子类方法
//若不重名,则完全继承父类方法
}
showSkill(){
return `技能:${this.skill}`
}
}
let s = new Student("吾爱博客","逃学")
//{name: "吾爱博客", skill: "逃学"}
//__proto__: Person
//constructor: class Student
//showName: ƒ showName()
//showSkill: ƒ showSkill()
//__proto__:
//constructor: class Person
//showName: ƒ showName()
//__proto__: Object
console.log(s.skill,s.name) // 逃学 吾爱博客
s.showName() // 逃学 吾爱博客