原型和原型链

Yuyang 前端小白🥬

原型

什么是原型?什么是构造函数、实例原型、实例?它们的关系是什么?

原型(Prototype)

每个 JavaScript 对象都有一个内部链接到另一个对象的引用,这个对象被称为原型。当试图访问一个对象的属性时,JavaScript 会首先在这个对象自身上寻找该属性,如果找不到,则会查找该对象的原型,如此递归下去,直到找到该属性或达到原型链的末端。

构造函数(Constructor)

构造函数是用于创建对象的函数。通过 new 操作符调用构造函数时,它会创建一个新对象,并将这个新对象的内部 [[Prototype]] 连接到构造函数的 prototype 属性。

1
2
3
4
5
function Person(name) {
this.name = name;
}

const person = new Person('Yuyang');

Person是构造函数,person是创建的对象

实例原型(Instance Prototype)

实例原型是由构造函数的 prototype 属性引用的对象。所有由该构造函数创建的实例对象都将共享这个实例原型对象的属性和方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Person(name) {
this.name = name;
}

Person.prototype.callName = function(){
console.log("zyz");
}

const person1 = new Person("Yuyang");
const person2 = new Person("YuyangA");
person1.callName = function(){
console.log(this.name)
}
person1.callName();
person2.callName();

实例(Instance)

实例是通过构造函数创建的具体对象。每个实例都有一个内部链接到它的构造函数的 prototype 属性的引用,这个链接可以通过 __proto__(非标准)或 Object.getPrototypeOf 方法来访问。

1
2
const person3 = new Person('Charlie');
console.log(person3.__proto__ === Person.prototype); // 输出:true

image.png

原型链

image.png

词法作用域

JavaScript 采用词法作用域(lexical scoping),也就是静态作用域。

this

JavaScript中的this总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境。

this的指向

  • 作为对象的方法调用
  • 作为普通函数调用
  • 构造器调用
  • Function.prototype.call 或 Function.prototype.apply 调用

作为对象的方法调用:

函数作为对象的方法被调用时,this指向该对象:

1
2
3
4
5
6
7
8
var obj = {
a: 1,
getA: function () {
alert(this === obj);
alert(this.a);
}
}
obj.getA();

作为普通函数调用:

当函数不作为对象的属性被调用时,此时的this总是指向全局对象。在浏览器的javascript里,这个全局对象是window对象。

1
2
3
4
5
6
7
8
window.name = "globalName";

var getName = function(){
return this.name;
}

console.log(getName()); //输出: globalName

1
2
3
4
5
6
7
8
9
10
11
12
window.name = "globalName";

var myObject = {
name: "sven",
getName: function(){
return this.name;
}
}

var getName = myObject.getName;
console.log(getName()); // globalName

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
window.id = "window";
document.getElementById("div1").addEventListener("click", function() {
console.log("🚀 ~ window.id:", this.id, this
var callback = function(){
console.log("🚀 ~ window.id:", this.id, this)
}       
callback()
})

// callback中的this指向window

document.getElementById("div1").addEventListener("click", function() {
console.log("🚀 ~ window.id:", this.id, this);
var callback = () => {
console.log("🚀 ~ window.id:", this.id, this);
};       
callback();
});

// 箭头函数没有this 捕获其上下文的this值

document.getElementById("div1").addEventListener("click", function() {
console.log("🚀 ~ window.id:", this.id, this);
var callback = function(){
console.log("🚀 ~ window.id:", this.id, this);
}.bind(this);       
callback();
});

// 使用 bind 显式地将 this 绑定到 div1

document.getElementById("div1").addEventListener("click", function() {
console.log("🚀 ~ window.id:", this.id, this);
var self = this;
var callback = function(){
console.log("🚀 ~ window.id:", self.id, self);
};       
callback();
});

// 暂存this

构造器调用:

用new调用函数时,该函数会返回一个对象,通常情况下,构造器的this就会指向返回的这个对象,

1
2
3
4
5
6
var MyClass = function(){
this.name = "seven";
}

var obj = new MyClass();
alert(obj.name);

当构造器显式的返回一个对象时,那么此次运算结果最终会返回这个对象,而不是我们之前期待的this

1
2
3
4
5
6
7
8
var MyClass = function(){
this.name = "sven";
return {
name: "anne"
}
}
var obj = new MyClass();
alert(obj.name); //anne

Function.prototype.call 或 Function.prototype.apply 调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
var obj1 = {
name: 'seven',
getName: function(){
return this.name;
}
}

var obj2 = {
name: 'anne'
}

console.log(obj1.getName()) // seven
console.log(obj1.getName.call(obj2)) // anne

call 方法调用一个函数,并显式地指定 this 值和传递的参数。

call源码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Function.prototype.myCall = function(context, ...args) {
// 如果没有提供 context,默认设置为全局对象(在浏览器中是 window)
context = context || globalThis;

// 为 context 创建一个唯一的临时属性来存储函数
const fnSymbol = Symbol();
context[fnSymbol] = this;

// 使用 context 调用函数,并传递参数
const result = context[fnSymbol](...args);

// 删除临时属性
delete context[fnSymbol];

return result;
};

// 示例使用 myCall
function greet(greeting, punctuation) {
console.log(greeting + ', ' + this.name + punctuation);
}

const person = { name: 'Alice' };
greet.myCall(person, 'Hello', '!'); // 输出: "Hello, Alice!"

call() 方法接受的是参数列表,而 apply() 方法接受的是一个参数数组

call:

function.call(thisArg, arg1, arg2, …)

apply:

func.apply(thisArg, [argsArray])

bind 方法创建一个新的函数,该函数在调用时,其 this 值和传递的参数被预先设置。与 call 方法不同,bind 不会立即调用函数,而是返回一个新的函数。

1
2
3
4
5
6
7
8
9
10
11
12
var foo = {
value: 1
}

Function.prototype.bind2 = function(context){
var _this = this;
return function(){
_this.apply(context);
}
}

var bindFoo2 = bar.bind2(foo);

丢失的this