javascript进阶系列(六)—— 原型

130 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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创建的独立对象。

  1. 事件处理函数中 - this指向事件源
  2. 计时器/定时器函数中 - this指向window对象
  3. 对象中函数调用 - 找准函数调用者
  4. 普通函数调用 - this指向window对象
  5. 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)
}