持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情
前言
大家好呀,我是L同学。在上篇文章javascript进阶系列(四)—— 构造函数中,我们学习了javascript中包装类型、构造函数等相关知识点。今天,在这篇文章中,我们继续学习prototype-原型属性、__proto__ 原型属性、this指向等相关知识点。
prototype-原型属性
构造函数本质上也是对象 所以函数名也有属性, 构造函数名.prototype就是原型属性。构造函数名.prototype的值, 是一个对象, 这个对象key对应的都是方法。prototype可以让所有实例化的对象都来共享这个prototype原型属性的值。prototype是函数特有的属性, 只有函数能使用。
在原型属性上添加, 让所有实例对象共享, 可以保证内存的不浪费。但是像基础属性, 数组, 对象应该是对象独有的, 只有功能一样的函数才能共享。
// 1. Person其实也是一个对象
function Person(){
}
// 查看Person的prototype属性 - 值是一个对象(原型对象)
console.log(Person.prototype);
// 2. 尝试在函数的原型属性上, 添加我们需要共享的数据
Person.prototype.sing = function(){
console.log("学习唱歌");
}
// 3. 尝试判断
var p1 = new Person();
var p2 = new Person();
console.log(p1.sing === p2.sing); // true
__proto__ 原型属性
__proto__是对象的一个属性。为了让对象能够调用更多的方法 (自身没有, 会通过这个属性隐式查找)。
对象的__proto__等于构造函数的prototype属性。
function Person(){
this.name = "";
this.age = 0;
}
Person.prototype.sing = function(){
console.log("学习唱歌");
}
// 1. 构造函数里的属性 - 直接在对象上
var per = new Person();
console.log(per); // {age: 0, name: "", __proto__: Object}
// 2. 构造函数prototype上的属性 - 绑定在了对象的__proto__属性上
console.log(per.__proto__); // {sing: ƒ (), constructor: ƒ Person(), __proto__: Object}
// "对象的__proto__" 等于 "构造函数的prototype属性"
console.log(per.__proto__ === Person.prototype);
原型链
原型链, 就是每个对象通过__proto__去寻找它自身没有的属性/方法来进行调用, 逐级查找, 直到对象的顶端, 没有就报错, 有就调用执行。函数有prototype, 目的是为了让所有实例对象共享方法使用, 节省内存。对象有__proto__, 目的为了让对象调用更多的属性和方法。
function Person(){
this.name = "";
this.age = 0;
}
Person.prototype.sing = function(){
console.log("学习唱歌");
}
var per = new Person();
// per调用一个不存在的方法
console.log(per.toString());
console.log(per);
this指向
万能口诀:谁调用就是谁 (除了 new 以外)。看好函数在调用时, 点前面的调用者。new调用函数时, 构造函数内this指向的是new创建的独立对象。
- 事件处理函数中 - this指向事件源
- 计时器/定时器函数中 - this指向window对象
- 对象中函数调用 - 找准函数调用者
- 普通函数调用 - this指向window对象
- new调用函数时, 构造函数内this指向的是new创建的独立对象
// 1. 事件处理函数中 - this指向事件源
var btn = document.getElementById("btn");
btn.onclick = function () {
console.log(this); // btn
}
// btn调用onclick方法执行, 调用者是btn
// 2. 计时器/定时器函数中 - this指向window对象
setTimeout(function () {
console.log(this); // window对象
}, 2000);
// window调用setTimeout上的函数执行
// 3. 对象中函数调用 - 找准函数调用者
var obj = {
a: {
fn: function () {
console.log(this); // a对象 {b: 10, fn: function(){}}
},
b: 10
}
}
obj.a.fn(); // 注意看好调用者是谁
// 4. 看好"调用"者
let obj2 = {
a: 10,
sing: function () {
return function () {
console.log(this) //this 指向的是window
}
}
}
obj2()()
// 执行的过程原理
let res = obj2.sing()
// console.log(res);
res()
// 注意: 第一行是访问fn的key对应的value, 但是函数并没有调用执行
// 所以真正的调用者在window.theFn()
// 5. 普通函数调用
function myFn() {
console.log(this); // window对象
}
myFn();
// 因为前面省略了window.
// 6. new 调用特殊记忆
function Person(){
console.log(this);
}
Person(); // 这样调用this是window
new Person(); // 这里this是一个空对象
// 总结:
// 看好函数在调用时, 点前面的调用者
// new调用函数时, 构造函数内this指向的是new创建的独立对象
构造函数中函数里this使用:
function Person(theName, theAge){
this.name = theName;
this.age = theAge;
}
Person.prototype.sayHello = function(){ // 定义函数
console.log(this); // 调用者就是per
console.log("我叫" + this.name + "我今年, " + this.age + "岁");
}
var per = new Person("小黑", 18);
per.sayHello(); // 调用函数
代码定义构造函数模板(不执行)。给Person的原型上添加sayHello方法, 声明函数(不执行)。new Person() 构造函数执行, 传入参数, 给空对象挂载属性和对应的值。per.sayHello() 调用sayHello方法执行, 访问this(也就是per)对象上的属性打印。
箭头函数的this指向:
箭头函数的this--箭头函数没有this指向,指向的是上一层作用域的this指向。
document.querySelector('button').onclick = () => {
console.log(this) // 不再指向事件源,指向的是父级作用域 window
}
// 等待2s之后更改button按钮的文本
document.querySelector('button').onclick = function () {
setTimeout(() => {
console.log(this) // 定时任务函数改成箭头函数之后this指向了上一层作用域的this
this.innerHTML = '文本'
}, 2000)
}