ES6学习之继承和原型链

每个函数实例对象都有一个属性指向自己的原型对象。
函数指向原型对象的属性是prototype

实例对象指向原型对象的属性是 __proto__
实例对象的 __proto__指向它的构造函数的原型对象prototype
构造函数的原型对象 prototype 也有一个自己的原型对象 __proto__

层层向上直到一个对象的原型对象为null。根据定义,null 没有原型,并作为这个原型链中的最后一环。

几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。

访问原型

虽然浏览器支持使用 object.__proto__ 访问对象的原型,但这不是JavaScript的标准。
ES6之后推荐使用 Object.getPrototypeOf(object) 获取实例对象原型,使用Object.setPrototypeOf(object, proto) 设置实例对象原型。

function Foo() {}
let obj = new Foo;
Foo.prototype.a = 1;
obj.__proto__.b = 2;
console.log(obj.__proto__ === Object.getPrototypeOf(obj)); // true
console.log(obj.__proto__ === Foo.prototype); // true
console.log(obj.__proto__ === obj.constructor.prototype); // true
console.log(obj.__proto__.a); // 1
console.log(Foo.prototype.b); // 2
// 注意不要如此直接定义原型对象来添加属性
Foo.prototype = {a:1,b:2}; // 这样会破坏原型链,原型对象prototype中还包含着它自己的原型对象

区分实例对象和构造函数的原型对象

new Foo()实例化的对象的原型对象 __proto__ 指向的是Foo的构造函数的原型对象 prototype
很多文章将两者都写作 prototype,需自行区分。

原型链

每个对象都有一个指向自己原型对象的链,当试图访问对象的属性时,现在对象本地属性上查找,如果找不到,就会在它的原型对象上查找(继承属性),依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。

属性遮蔽
下面的示例,访问 o.b 时,在本地属性中查找到 属性 b(2),即返回。并没有使用到原型中继承的属性 b(3),这种情况被称为属性遮蔽

function f() {
   this.a = 1;
   this.b = 2;
}
let o = new f(); // {a: 1, b: 2}

// 在f函数的原型上定义属性
f.prototype.b = 3;
f.prototype.c = 4;

// 属性遮蔽
console.log(o.b); // 2

// o的原型链:
// {a:1,b:2} --> {b:3,c:4} -- > Object.prototype --> null
let proto = o;
console.log(o);
do {
	proto = Object.getPrototypeOf(proto);
	console.log(proto);
} while(proto)

本地属性 && 继承属性

本地属性、自有属性、自身的属性:对象自己创建,而不是从原型上继承的属性。
继承属性:对象自身没有,但可以从原型链中继承的属性。

function f() {
   this.a = 1;
   this.b = 2;
   this.show = 
}
let o = new f(); // {a: 1, b: 2}
f.prototype.b = 3;
f.prototype.c = 4;

// hasOwnProperty校验本地属性中是否有匹配的属性名
console.log(o.hasOwnProperty('a')); // true
console.log(o.hasOwnProperty('c')); // false

// defineProperty定义或修改对象的属性,一般在构造器对象上调用此方法
// 而不是在任意一个 Object 类型的实例上调用。
Object.defineProperty(o, prop, descriptor);

this指向

当继承的函数被调用时,this 指向的是当前继承的对象,而不是继承的函数所在的原型对象。
注意箭头函数 的this指向声明函数时的上下文。

var b = '这是全局定义的变量b';
function f() {
   this.a = 1;
   this.b = 2;
}
let o = new f();

f.prototype.b = 3;
f.prototype.c = 4;
f.prototype.show = () => {
    console.log(this.b);
};
f.prototype.show2 = function() {
    console.log(this.b);
};

o.show(); // 这是全局定义的变量b
o.show2(); // 2

类 构造函数语法糖

ES6提供一个让对象原型写法更加清晰的语法糖
新的关键字包括:class,extends,constructor,super,static。

class:声明创建一个类(构造器)
extends 在类声明或类表达式中,创建一个类的子类。
constructor:类的构造方法,用于初始化用class创建的实例对象的特殊方法。
— 如果不指定,则使用一个默认的构造函数 constructor() {}
— 子类不指定构造方法,则使用默认的构造函数 constructor(…args) { super(…args); }
super:用于调用和访问 父对象/父类/原型 的一个方法
— super([arguments]) // 调用 父对象/父类/原型 的构造方法。
— super.functionName([arguments]) // 调用 父对象/父类/原型 上的方法。
— super可以在静态方法中调用 父对象/父类/原型 上的静态方法。
— 只限调用和访问,不能修改和删除。
— 只限访问方法,不能访问属性。
static:在class内部定义静态方法,不允许类创建的实例调用,只能在类内部调用。
— 在静态方法中调用类自身的其他静态方法用this.staticFuncName
— 非静态方法中调用类自身的静态方法,不能用this。只能使用:
------- 类名:className.funcName
------- 构造函数:this.constructor.funcName

使用:
1.创建

// 类表达式
let Person = class Man {}; // 命名表达式,在类体内部才能访问到这个名字
let Person2 = class {}; // 匿名表达式
// 类声明
class Person {}

2.定义

class Person {
	// 构造函数,初始化实例的属性。不定义则使用默认构造函数
	constructor(name='') {
		this.name = name || ''; // 定义实例的属性
		this.age = 30;
        // 构造函数中调用公共方法和静态方法方式与demo()中一样
	}
	setName (val) {
		this.name = val;
	} // 定义实例的方法
	static static_setName(val) {
        // this指向Person
        this.static_setName_setName(val); // 静态方法内调用静态方法
	} // 仅限类使用的静态方法
	static static_setName_setName(val) {
        // this指向Person
        // 无法动态获取实例对象
        // 一般不在静态方法中操作实例的属性和调用公共方法
        this.prototype.name = val;
	}
	demo () {
		this.setName('乔峰'); // 调用公共方法
		// this.constructor.static_setName('乔峰'); // 调用静态方法
		// Person.static_setName('乔峰');  // 调用静态方法的另一个方式
	}
	/* 错误写法:
	setName = function setName() {}
	*/
	
}
let jack = new Person();
jack.demo()
console.log(jack)

3.继承

// 接[2.定义]示例中的代码
class Man extends Person {
    constructor(name) {
        // 子类指定了构造函数,必须执行super(),否则报错
        // 必须在调用this之前执行,否则报错
        super(name);
        this.brother = '段誉';
        this.changeName('阿朱')
        Man.static_changeName('钟灵')
    }
    changeName(val) {
        super.setName(val); // 调用父类的公共方法
    }
    static static_changeName(val) {
        super.static_setName(val); // 调用父类的静态方法
    }
}
let mike = new Man();
console.log(mike)

规避错误:

类名不能重复
类体内部默认声明了严格模式
类声明不能提升,与函数声明不同
子类构造函数中不调用super会报错,在调用super之前使用this也会报错
不能删除或修改使用super访问的方法,且super只能访问方法,不能访问属性

class Person {}
class Person {}
// 类名重复,报错 Identifier 'Person' has already been declared
// 类体内部默认声明了严格模式
class Person {
    constructor() {
        this.sex = 'man';
        this.age = 30;
    }
    bar() {
        var donotdelete = 1;
        delete donotdelete; // 报错:Delete of an unqualified identifier in strict mode.
    }
}
// 与函数声明不同,类声明不能提升
let jack = new Person; // 报错:Cannot access 'Person' before initialization
class Person {
    constructor() {
        this.sex = 'man';
        this.age = 30;
    }
}
// 不能删除或修改使用super访问的方法,且super只能访问方法,不能访问属性
class Man{
    constructor(){
        this.name = '乔峰'
    }
    show() {
        console.log(this.name)
    }
}
class Person extends Man {
    constructor() {
    	// this.name = '乔峰'; // 在super调用前使用this报错
        super(); // 此行不执行会报错
        this.sex = 'man';
        this.age = 30;
    }
    bar() {
        console.log(super.name); // undefined
        console.log(super.show); // function(){...}
        super.show(); // 乔峰
        console.log(this.name); // 乔峰
    }
}
let jack = new Person;
jack.bar()

创建对象和生成的原型链

let str = 'abcd';
// str --> String.prototype --> Object.prototype --> null

let arr = [1,2,3];
// arr --> Array.prototype --> Object.prototype --> null

let obj = {a:1};
// obj --> Object.prototype --> null

let num = 1;
// num --> Number.prototype --> Object.prototype --> null

// 使用构造器(构造函数)创建的对象
function Foo() {
    this.a = 1;
}
let obj_foo = new Foo();
// obj_foo --> Foo.prototype --> Object.prototype --> null

// 使用Object.create(proto)创建的对象 proto:新对象的原型
let a = {a:1};
let b = Object.create(a);
console.log(b.hasOwnProperty('a')); // false
// b --> a --> Object.prototype --> null
let c = Object.create(null); // 什么也没继承
console.log(c.hasOwnProperty); // undefined
// c --> null
// 使用class关键字创建的对象
class Person {
	constructor() {
		this.name = '乔峰';
		this.age = 30;
	}
	setName (val) {
		this.name = val;
	}
	static static_add(num) {
        return num++;
	}
}

class Man extends Person {
    constructor() {
        super(name);
        this.brother = '段誉';
    }
    changeName(val) {
        super.setName(val); // 调用父类的公共方法
    }
}
let mike = new Man();
// mike -- > Man.prototype --> Person.prototype --> Object.prototype --> null

性能

在原型链上查找属性比较耗时,对性能有副作用。当查找不存在的属性时,还会遍历整个原型链。
可以使用 hasOwnProperty 或 Object.keys() 确认属性是否是本地属性。

参考:
继承与原型链 MDN
类声明
类表达式

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值