持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第11天,点击查看活动详情
原型 prototype
function Person(name, age) {
this.name = name;
this.age = age;
this.sayHi = function() {
console.log('hi, 我是' + this.name + ', 我' + this.age + '了');
}
}
Person.prototype.sayHello = function() {
console.log('hello, 我是' + this.name + ', 我' + this.age + '了');
}
console.log(Person.prototype);
let p1 = new Person('shaosiming', 18);
let p2 = new Person('dasiming', 19);
console.log(p1.sayHi == p2.sayHi); // false
console.log(p1.sayHello == p2.sayHello); // true
可以看到, 如果不使用原型那么每个对象都有自己的方法, 这样会很大的浪费内存资源 像sayHi这样的方法, 是属性每个对象的
而有了原型之后, 我们可以在原型上添加一个公用的方法, 这样就节省了内存资源
原型链的使用流程
- 当一个对象调用方法和访问属性时, 首先会在自己本身查找, 如果找到则调用或访问
- 如果在自身找不到属性和方法时, 会去自身的原型上查找, 如果找到则调用或访问
- 如果在自身的原型上找不到属性和方法时, 则去原型的原型上查找, 如果找到则调用或访问
- 按照这个规则, 在原型链上一路向上
- 直到Object这个根原型, 如果还没有找到, 则返回undefin
作用
- 使用原型后, 我们可以把公有的属性和方法放到原型对象上去, 这样节省内存资源
- 在使用原型后, 在给原型添加一个新方法或属性时, 拥有该原型的所有对象也自动获得了该属性和方法, 哪怕这些对象的创建是在添加这个方法和属性之前
this
在方法中this指向调用这个方法的对象.
而在如果在对象中没有找到要调用的方法, 而在原型中找到了要调用的方法, this还是指向调用方法的对象.
Person.prototype.isSleeping = false;
Person.prototype.sleep = function() {
this.isSleeping = true;
}
console.log(p1.isSleeping); // false
console.log(p2.isSleeping); // false
console.log(p1.hasOwnProperty('isSleeping')); // false
console.log(p2.hasOwnProperty('isSleeping')); // false
p1.sleep();
console.log(p1.isSleeping); // true
console.log(p2.isSleeping); // false
console.log(p1.hasOwnProperty('isSleeping')); // true
console.log(p2.hasOwnProperty('isSleeping')); // false
hasOwnProperty方法: 查看对象自身有没有某个属性
注意: 这里一开始的时候p1和p2都没有isSleeping属性, isSleeping属性都是在它们的原型上
而当p1调用了sleep方法之后, 在该方法中this指向调用者, 也就是p1, 因此向p1添加了属性isSleeping并设置为true
而p2没有调用sleep方法, 因此也就没有向p2对象中添加isSleeping属性, 这个属性在原型上
原型链
在js中可以通过原型这种方式来实现常见面向对象中的继承这个特性
比如我们要让Student继承自Person, 在js中想要实现这种效果, 要按照如下步骤进行
- 有一个Peron构造函数, 有一个Student构造函数
- 设置Student的prototype为new Person()的空对象
- 如果想要实现的完全一点还要设置Student的prototype的constructor为Student构造函数
注意: 在Student的构造函数中, 我们可以请求Person构造函数来帮我们初始化实例, 这里要使用Person.call()这种调用方式
原型链的终点永远都是Object这个对象
uml图
代码
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHi = function() {
console.log('Person sayHi');
}
function Student(name, age, classNo) {
// 要使用这种调用方式, 原指定Person构造函数中的this
Person.call(this, name, age);
this.classNo = classNo;
}
// 在在这里设置Student的原型为通过Person构造的空对象
Student.prototype = new Person();
// 这里要设置Student的原型的构造函数为Student, 否则它是指向的Person这个构造函数
Student.prototype.constructor = Student;
Student.prototype.study = function() {
console.log(`${this.name}正在${this.classNo}班学习`);
}
let stu = new Student('shaosiming', 18, 100);
console.log(stu);
console.log('stu constructor: ', stu.constructor);
stu.sayHi();
stu.study();
给内置对象添加属性或者方法
有了原型, 我们可以给内置的对象添加属性或者方法, 简直不要太方便
// 给内置对象添加方法
let arr = ['wow', 'haha', 'xixi', 'abcdcba']
String.prototype.palindrome = function() {
let lastIdx = this.length - 1;
for (let i = 0; i <= lastIdx; i++) {
if (this[i] !== this[lastIdx - i]) {
return false;
}
return true;
}
}
String.prototype.palindrome2 = function() {
let reverseStr = this.split('').reverse().join('');
return reverseStr === this.valueOf()
}
for (let i = 0; i < arr.length; i++) {
const element = arr[i];
if (element.palindrome2()) {
console.log(`${arr[i]} is a palidrome`);
} else {
console.log(`${arr[i]} is not a palidrome`);
}
}