持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情
前言
JavaScript的原型和原型链,类和继承,是JavaScript的核心重点,理解了对像的这些关系,我们后面看优秀的代码框架是就从大的方面入手,可以层层深入分析。基于原型是JavaScript的高级核心部分,ES6中的Clas面向对象也是居于原型链的语法糖。
原型和原型链
JavaScript中的对象
像下面第一的字面量对象obj对象一样,JavaScript中对象一般是键值对的形式, 健的类型有限制,值可以是任意类型,如果要以哈希的形式使用的话还是建议用Map对象,可参考下面文章。
// 创建对象字面量
let obj = {
prop1: 123,
prop2: 'abc',
prop3: [1, 2, 3],
prop4: function () { },
prop5: {}
};
可以像下面这样添加一个全新的属性
let obj = {
prop1: 123,
prop2: 'abc',
prop3: [1, 2, 3],
prop4: function () { },
prop5: {}
};
obj.prop6 = 'hello world'; // (1)新增属性
console.log(obj);
为什么说JavaScript是基于对象的和原型的看下面结果
console.log(Array instanceof Object); // true
console.log(String instanceof Object); // true
console.log(Function instanceof Object); // true
console.log(Number instanceof Object); // true
console.log(Math instanceof Object); // true
console.log(RegExp instanceof Object); // true
console.log(Boolean instanceof Object); // true
console.log(Symbol instanceof Object); // true
console.log(Date instanceof Object); // true
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
\
理解原型
代码世界的思想里,重复造轮子是要避免的,而轮子的最小元素就是像属性和方法了,而OOP思想中继承这一方法就有这样的功能,而JavaScript中原型就是有扩展和复用的继承属性,所以要重视。
使用setPrototypeOf() 设置对象的方式实现继承
liek this
没有设置tom对象为gogo 对象的原型时,gogo访问eglish属性时undefined,
Object.setPrototypeOf(gogo, tom) 后可以访问english属性,并打印出'hello world'
const tom = { english: 'hello world' };
const jack ={ china: '你好世界'}
const gogo = { language: 'gogo'}
console.log(gogo.english); // undefined
Object.setPrototypeOf(gogo, tom)
console.log(gogo.english); // 'hello world'
在上面代码的基础上,我们可以看到,gogo的原型只能是tom或者jack, 这次使用Object.setPrototypeOf(gogo, jack)的时候,就变成这样了console.log(gogo.english); // undefined
const tom = { english: 'hello world' };
const jack ={ china: '你好世界'}
const gogo = { language: 'gogo'}
console.log(gogo.english); // undefined
Object.setPrototypeOf(gogo, tom)
console.log(gogo.english); // 'hello world'
// 新增部分
console.log(gogo.china); // undefined
Object.setPrototypeOf(gogo, jack)
console.log(gogo.china); // 你好世界
console.log(gogo.english); // undefined
不用担心,我们可以这样做
我们只需要在下面【1】的地方改为让tom的原型为Jack,而gogo的原型为tom,这样gogo就可以顺着网线去找你了哈哈调皮了, 就可以顺着原型链找到对应的属性了, 下面我们还会详细的说说啥是原型链
const tom = { english: 'hello world' };
const jack ={ china: '你好世界'}
const gogo = { language: 'gogo'}
console.log(gogo.english); // undefined
Object.setPrototypeOf(gogo, tom)
console.log(gogo.english); // 'hello world'
console.log(gogo.china);
Object.setPrototypeOf(tom, jack) // 【1】------- 让tom ,继承jack-----
console.log(gogo.china); // 你好世界
console.log(gogo.english); // hello world
注意区分
prototype是对象的原型,而[[Prototype]]或__proto__是对象的原型对象属性
使用Object.create()实现继承
使用 Object.create(null) 创建对象会创建一个完全的空对象,
// 创建一个原型为 null 的空对象
let o = Object.create(null);
要区别这两种方式
let o = {};
// 以字面量方式创建的空对象就相当于:
let o = Object.create(Object.prototype);
例如:
const person = {
isHuman: false,
printIntroduction: function () {
console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
},
foo: 123,
bar: 'abc',
sing() {
return 'I can sing'
}
};
const me = Object.create(person);
me.name = 'Matthew'; // "name" is a property set on "me", but not on "person"
me.isHuman = true; // inherited properties can be overwritten
me.printIntroduction();
console.log(me.foo); // 123
console.log(me.sing()); // I can sing
// expected output: "My name is Matthew. Am I human? true"
构造函数与原型
我们可以定义一个Person构造函数,然后它又name和age属性,有唱舞rap和selfIntroduction的方法。,有点和面向对象的class 方式相似, 我们通过new 一个新的对象,实现了对象的继承,我们只需要定义一个构造函数,后面就可以new很多个对象了.
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sing = function () {
return 'I can sing'
}
Person.prototype.dance = function () {
return 'I can dance'
}
Person.prototype.rap = function () {
return 'I can rap'
}
Person.prototype.selfIntroduction = function () {
return `Hi I'am ${this.name}, ${this.age} years old this year, ${this.sing()},${this.dance()}, ${this.rap()}`
}
let me = new Person('周杰伦', 18)
let you = new Person('周星驰', 28)
let she = new Person('王心凌', 18)
console.log(me.sing()); // I can sing
console.log(you.selfIntroduction()); // Hi I'am 周星驰, 28 years old this year, I can sing,I can dance, I can rap
console.log(she.selfIntroduction()); // Hi I'am 王心凌, 18 years old this year, I can sing,I can dance, I can rap
手写一个new方法
new 关键字会进行如下的操作:
- 1、创建一个空的简单JavaScript对象(即{});
- 2、为步骤1新创建的对象添加属性__proto__,将该属性链接至构造函数的原型对象 ;
- 3、将步骤1新创建的对象作为this的上下文 ;
- 4、如果该函数没有返回对象,则返回this。、
function my_new(fn) {
let obj = {} // (1)
if (fn.prototype !== null) {
obj.__proto__ = fn.prototype // (2)
}
let res = fn.apply(obj, Array.prototype.slice.call(arguments, 1)) // (3)
if ((typeof res === 'object' || typeof res === 'function') && res !== null) {
return res
}
return obj
}
// test
function Foo(){
this.a = 123; // 123
this.b = 'hello'
}
let test = my_new(Foo)
console.log(test.a, test.b); // 123 hello
let res = new Foo()
console.log(res.a, res.b); // 123 hello
理解原型链
先看两张图丫丫金,
好理解吗
这样会不会明白一些
function Foo() {}
Foo.prototype.sayHi = function () {
return 'Hello'
}
let foo = new Foo()
console.log(foo.__proto__ === Foo.prototype); // true
console.log(Foo.__proto__ === Function.prototype); // true
console.log(Function.__proto__ === Object.__proto__); // true
console.log(foo instanceof Foo); // true
console.log(Foo instanceof Function); // true
console.log(Function instanceof Object); // true
理解原型链,需要动手实践一遍,就可以理解清楚了,在控制台执行里面代码,然后一层层拨开原型链的面纱,找到它们的继承关系,
function Foo() { }
Foo.prototype.sayHi = function () {
return 'Hello'
}
Foo.prototype.money = '1000000'
Function.prototype.naiveFunc = function () {
return 'I am Function'
}
Object.prototype.navieObj = function () {
return 'I ma navieObj'
}
let foo = new Foo()
console.log(foo.navieObj()); // I ma navieObj
console.log(Foo.naiveFunc()); // I am Function
console.log('-------------foo-------------------');
console.log('foo:',foo);
console.log('foo.constructor:',foo.constructor);
console.log('foo.__proto__:',foo.__proto__);
console.log('foo.prototype:',foo.prototype);
// console.log(foo.prototype.__proto__); error
console.log('-------------Foo-------------------');
console.log('Foo:',Foo);
console.log('Foo.__proto__:',Foo.__proto__);
console.log('Foo.constructor:',Foo.constructor);
console.log('Foo.prototype:',Foo.prototype);
console.log('Foo.prototype.__proto__:',Foo.prototype.__proto__);
console.log('-------------Function-------------------');
console.log('Function:',Function);
console.log('Function.__proto__:',Function.__proto__);
console.log('Function.constructor:',Function.constructor);
console.log('Function.prototype:',Function.prototype);
console.log('Function.prototype.__proto__',Function.prototype.__proto__);
console.log('-------------Object-------------------');
console.log('Object:',Object);
console.log('Object.constructor:',Object.constructor);
console.log('Object.__proto__:',Object.__proto__);
console.log('Object.prototype:',Object.prototype);
console.log('Object.prototype.__proto__:',Object.prototype.__proto__);
执行后结果是这样的
我们展开foo的原型对象属性
foo.__proto__如下
它们的指向关系简化一下就是:
foo.__proto__ ->Foo.prototype -> Foo.prototype.__proto__ ->Object.prototype -> Object.prototype.__proto__ -> null
对象的继承查找路线大概是这样,查找某个值的时候,如果一条链查完都没有,就返回null啦
类和继承
类的定义
我们看看下面类的定义,是不是和上面的构造函数很像,这里我们注意到constructor 和static 关键字。 constructor 其实就是Person的构造函数,static是来定义方法的关键字,相当于把方法添加到构造函数这个对象里,其他的方法是添加到对象的原型上的。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
static konfu() {
return 'I can play konfu'
}
sing() {
return 'I can sing'
}
dance() {
return 'I can dance'
}
rap() {
return 'I can rap'
}
selfIntroduction() {
return `I am ${this.name}, ${this.age} years old this year`
}
}
let person = new Person('李白', 20)
console.log(person.selfIntroduction()); // I am 李白, 20 years old this year
console.log(Person.konfu()); // I can play konfu
console.log(person.konfu()); // error
class 其实是语法糖,转化编译的时候,其实代码会变成下面这样
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.konfu = function () {} // (1) 使用static 关键字声明的静态方法
Person.prototype.sing = function () {} // 普通原型上的方法
.
.
.
类的继承
class 继承参考文章 通过
extends实现对父类的继承
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
static konfu() {
return 'I can play konfu'
}
sing() {
return 'I can sing'
}
dance() {
return 'I can dance'
}
rap() {
return 'I can rap'
}
selfIntroduction() {
return `I am ${this.name}, ${this.age} years old this year`
}
}
class Monkey extends Person {
eatBanana() {
return 'I can eat banana'
}
}
let person = new Person('李白', 20)
let monkey = new Monkey('美猴王', 500)
console.log(person.selfIntroduction()); // I am 李白, 20 years old this year
console.log(monkey.selfIntroduction()); // I am 美猴王, 500 years old this year
console.log(monkey.eatBanana()); // I can eat banana
console.log(person.eatBanana()); // error
总结
JavaScript的原型和原型链,类和继承,是JavaScript的核心重点,理解了对像的这些关系,我们后面看优秀的代码框架是就从大的方面入手,可以层层深入分析。
这里介绍的还不够全面,后面找时间,把原型和原型链,类和继承的细节部分,在做深入。
看完三件事!!!
graph TD
点赞 --> 关注 --> 评论