原型
原创2022年7月30日
导读
原型相关的笔记
prototype原型
1.原型是什么?
- 原型是一个对象,我们也称为
prototype
为原型对象。
2.原型的作用是什么?
- 共享方法
- 避免多次开通内存空间,减小内存占用
一般情况下,我们的公共属性定义到构造函数里面, 公共的方法我们放到原型对象身上
function Star(uname, age) {
this.uname = uname;
this.age = age;
// 不建议这样挂载方法
// this.sing = function() {
// console.log('我会唱歌');
// }
}
// 建议这样将公共的方法挂载到Star的原型链上,这样以后的每个实例都可以访问
Star.prototype.sing = function () {
console.log('我会唱歌');
}
console.dir(Star)
- 打印一下
Star
会出现下面的样子 - 将Start展开会出现待展开的
prototype
- 再次展开又会出现一个
prototype
因为每一个原型上都有一个 prototype ,所以还可以再套娃(目前只是玩玩儿,可用性待发掘)
Star.prototype.sing.prototype.sing2 = function () {
console.log('我会唱歌2');
}
- 继续打印
接下来创建实例,调用方法
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
console.log(ldh.sing === zxy.sing);
ldh.sing();
zxy.sing();
- 控制台输出
- 所以可以将实例对象都能使用的方法,挂载到实例的
prototype
上,无论new了多少个实例,都可以实现数据共享 - 简单的说就是让他们都指向同一片内存空间,所以
ldh.sing === zxy.sing
才会为true
是因为都同时指向同一个内存空间 - 一般情况下,我们的公共属性定义到构造函数里面,公共方法我们放在原型对象身上
只有构造函数本身才有 prototype 这个属性
console.log(Star.prototype)
__proto__
对象原型对象身上系统自己添加一个 __proto__
指向我们构造函数的原型对象 prototype
对象原型只有__proto__
没有 prototype
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
Star.prototype.sing = function () {
console.log('我会唱歌');
}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
console.log(ldh.__proto__); // 对象身上系统自己添加一个 __proto__ 指向我们构造函数的原型对象 prototype
console.log(Star.prototype);
ldh.sing();
ldh.__proto__.sing2 = () => {
console.log('我会唱歌2');
}
ldh.sing2();
console.log(ldh.__proto__ === Star.prototype);
// 方法的查找规则: 首先先看ldh 对象身上是否有 sing 方法,如果有就执行这个对象上的sing
// 如果么有sing 这个方法,因为有__proto__ 的存在,就去构造函数原型对象prototype身上去查找sing这个方法
原型对象和对象原型
- 我们称为
prototype
为原型对象,__proto__
为对象原型 __proto__
对象原型和原型对象prototype
是等价的__proto__
对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象prototype
对象原型
function Star(uname, age) {
this.uname = uname;
this.age = age;
}
// 很多情况下,我们需要手动的利用constructor 这个属性指回 原来的构造函数
// Star.prototype.sing = function () {
// console.log('我会唱歌');
// };
// Star.prototype.movie = function () {
// console.log('我会演电影');
// }
Star.prototype = {
// 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
constructor: Star,
// 不加上面的constructor,则会报错,因为我们的原型对象指回的是Star,而不是Star.prototype
// 不加 constructor 再使用 Star.prototype = {} 就会将原来的Star构造函数给覆盖掉了
sing() {
console.log('我会唱歌');
},
movie() {
console.log('我会演电影');
}
}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
console.log(Star.prototype);
console.log(ldh.__proto__);
console.log(Star.prototype.constructor);
console.log(ldh.__proto__.constructor);
最终输出
如果把constructor: Star
注释掉
Star.prototype = {
// 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
// constructor: Star,
// 不加上面的constructor,则会报错,因为我们的原型对象指回的是Star,而不是Star.prototype
// 不加 constructor 再使用 Star.prototype = {} 就会将原来的Star构造函数给覆盖掉了
sing() {
console.log('我会唱歌');
},
movie() {
console.log('我会演电影');
}
}
打印输出
结论:
- 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
构造函数、实例、原型对象三者之间的关系
原型链
JavaScript 的成员查找机制(规则)
- 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性
- 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)。
- 如果还没有就查找原型对象的原型(Object的原型对象)。
- 依此类推一直找到 Object 为止(null)。
__proto__
对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
原型对象this指向
向我们实例对象.
原型对象里面放的是方法, 这个方法里面的this 指向的是 这个方法的调用者, 也就是这个实例对象.
扩展内置对象
每一个内置对象都会在 prototype
挂载对应的方法
console.log(Array.prototype);
console.log(Object.prototype);
console.log(Number.prototype);
console.log(String.prototype);
console.log(Boolean.prototype);
console.log(Date.prototype);
console.log(RegExp.prototype);
console.log(Error.prototype);
console.log(Symbol.prototype);
我们也可以自定义方法挂载到内置对象的 prototype
上
Array.prototype.sum = function () {
var sum = 0;
for (var i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
};
var arr = [1, 2, 3];
console.log(arr.sum());
console.log(Array.prototype);
call方法
call()
可以调用函数call()
方法是Function
原型上的,所以要通过方法.call('第一个参数为要将this指向的作用域',...args)
function fn(x, y) {
console.log('我想喝手磨咖啡');
this.testSum(x, y)
console.log(this);
}
const fn2 = () => {
console.log(this);
}
var o = {
name: 'andy',
testSum(x, y) {
console.log('值为' + (x + y));
}
};
// 1. call() 可以调用函数
// fn.call();
// 2. call() 可以改变这个函数的this指向 此时这个函数的this 就指向了o这个对象
fn.call(o, 321, 2)
- 上图我们可以看到,在使用
fn.call(o, 321, 2)
后,将fn
的this
指向了o
这个对象 - 我们在
o
上挂载一个testSum
方法,在fn
内通过this.testSum()
调用,能在控制台中输出结果
借用父构造函数继承属性
// 借用父构造函数继承属性
// // 1. 父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
// 2 .子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age);
this.score = score;
}
var son = new Son('刘德华', 18, 100);
console.log(son);
// 传入对象形式的构造函数
function Father(obj) {
// this 指向父构造函数的对象实例
this.uname = obj.uname;
this.age = obj.age;
}
// 2 .子构造函数
function Son(obj) {
// this 指向子构造函数的对象实例
Father.call(this, {
uname: obj.uname,
age: obj.age
});
this.score = obj.score;
}
var son = new Son({
uname: '刘德华',
age: 18,
score: 100
});
console.log(son);
利用原型对象继承方法
// 借用父构造函数继承属性
// 1. 父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
Father.prototype.money = function () {
console.log(100000);
};
// 2 .子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age);
this.score = score;
}
// 使用这样赋值,会将 Father.prototype 父构造函数的内存地址赋值给 Son.prototype 子构造函数,那么接下来给子构造函数原型上添加方法,父构造函数也会出现
// Son.prototype = Father.prototype; //这样直接赋值会有问题, 如果修改了子原型对象, 父原型对象也会跟着一起变化
Son.prototype = new Father();
// 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数
Son.prototype.constructor = Son;
// 这个是子构造函数专门的方法
Son.prototype.exam = function () {
console.log('孩子要考试');
}
var son = new Son('刘德华', 18, 100);
console.log(son);
console.log(Father.prototype);
Father.prototype.money()
son.money()
son.exam()
console.log(Son.prototype.constructor);
/**
* 如果使用 Son.prototype = Father.prototype; 下面会输出 true
* 如果使用 Son.prototype = new Father(); 下面会输出 false
*/
console.log(Son.prototype === Father.prototype);
Loading...