前端面试当中 js原型及原型链常考知识点
一、构造函数
讲原型则离不开构造函数,让我们先来认识下构造函数。
1.1 构造函数分为 实例成员 和 静态成员
让我们先来看看他们分别是什么样子的。
实例成员: 实例成员就是在构造函数内部,通过this添加的成员。实例成员只能通过实例化的对象来访问。
静态成员: 在构造函数本身上添加的成员,只能通过构造函数来访问
function Star(name,age) {
//实例成员
this.name = name;
this.age = age;
}
//静态成员
Star.sex = '女';
let stars = new Star('小红',18);
console.log(stars); // Star {name: "小红", age: 18}
console.log(stars.sex); // undefined 实例无法访问sex属性
console.log(Star.name); //Star 通过构造函数无法直接访问实例成员
console.log(Star.sex); //女 通过构造函数可直接访问静态成员
2.2 通过构造函数创建对象
该过程也称作实例化
2.2.1 如何通过构造函数创建一个对象?
function Father(name) {
this.name = name;
}
let son = new Father('Lisa');
console.log(son); //Father {name: "Lisa"}
此时,son就是一个新对象。
2.2.2 new一个新对象的过程,发生了什么?
(1) 创建一个空对象 son {}
(2) 为 son 准备原型链连接 son.__proto__ = Father.prototype
(3) 重新绑定this,使构造函数的this指向新对象 Father.call(this)
(4) 为新对象属性赋值 son.name
(5) 返回this return this
,此时的新对象就拥有了构造函数的方法和属性了
二、原型
前面我们在 实例化 和 实例共享方法 时,都提到了原型。那么现在聊聊这个神秘的原型到底是什么?
2.1 什么是原型?
Father.prototype 就是原型,它是一个对象,我们也称它为原型对象。
2.2 原型的作用是什么?
原型的作用,就是共享方法。
我们通过 Father.prototype.method
可以共享方法,不会反应开辟空间存储方法。
2.3 原型中this的指向是什么?
原型中this的指向是实例。
三、原型链
3.1 什么是原型链?
原型与原型层层相链接的过程即为原型链。
3.2 原型链应用
对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有__proto__原型的存在
每个对象都有__proto__原型的存在
function Star(name,age) {
this.name = name;
this.age = age;
}
Star.prototype.dance = function(){
console.log('我在跳舞',this.name);
};
let obj = new Star('张萌',18);
console.log(obj.__proto__ === Star.prototype);//true
3.3 原型查找方式
function Star(name) {
this.name = name;
(1)首先看obj对象身上是否有dance方法,如果有,则执行对象身上的方法
this.dance = function () {
console.log(this.name + '1');
}
}
(2)如果没有dance方法,就去构造函数原型对象prototype身上去查找dance这个方法。
Star.prototype.dance = function () {
console.log(this.name + '2');
};
(3)如果再没有dance方法,就去Object原型对象prototype身上去查找dance这个方法。
Object.prototype.dance = function () {
console.log(this.name + '3');
};
(4)如果再没有,则会报错。
let obj = new Star('小红');
obj.dance();
(1)首先看obj对象身上是否有dance方法,如果有,则执行对象身上的方法。
(2)如果没有dance方法,就去构造函数原型对象prototype身上去查找dance这个方法。
(3)如果再没有dance方法,就去Object原型对象prototype身上去查找dance这个方法。
(4)如果再没有,则会报错。
3.4原型的构造器
原型的构造器指向构造函数。
function Star(name) {
this.name = name;
}
let obj = new Star('小红');
console.log(Star.prototype.constructor === Star);//true
console.log(obj.__proto__.constructor === Star); //true
3.4.1 在原型上添加方法需要注意的地方
方法1:构造函数.prototype.方法
在原型对象上直接添加方法,此时的原型对象是有constructor
构造器的,构造器指向构造函数本身
function Star(name) {
this.name = name;
}
Star.prototype.dance = function () {
console.log(this.name);
};
let obj = new Star('小红');
console.log(obj.__proto__); //{dance: ƒ, constructor: ƒ}
console.log(obj.__proto__.constructor); // Star
3.4.2一般不允许直接改变原型prototype
指向
改变原型指向,会使原生的方法都没了,所以Array、String这些内置的方法是不允许改变原型指向的。如果改变了,就会报错。
Array.prototype.getSum = function (arr) {
let sum = 0;
for (let i = 0; i < this.length; ++i) {
sum += this[i];
}
return sum;
};
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(arr.getSum());//45
如果改变prototype指向,会报错!
Array.prototype = {
getSum: function (arr) {
let sum = 0;
for (let i = 0; i < this.length; ++i) {
sum += this[i];
}
return sum;
}
};
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(arr.getSum());//45
四.继承 - ES5方法
ES6之前并没有给我们提供extends继承,我们可以通过构造函数+原型对象模拟实现继承。
继承属性,利用call改变this指向。但该方法只可以继承属性,实例不可以使用父类的方法。
function Father(name) {
this.name = name;
}
Father.prototype.dance = function () {
console.log('I am dancing');
};
function Son(name, age) {
Father.call(this, name);
this.age = age;
}
let son = new Son('小红', 100);
son.dance(); //报错
如何继承父类的方法呢?
解决方法1:利用Son.prototype = Father.prototype
改变原型指向,但此时我们给子类增加原型方法,同样会影响到父类。
function Father(name) {
this.name = name;
}
Father.prototype.dance = function () {
console.log('I am dancing');
};
function Son(name, age) {
Father.call(this, name);
this.age = age;
}
Son.prototype = Father.prototype;
//为子类添加方法
Son.prototype.sing = function () {
console.log('I am singing');
};
let son = new Son('小红', 100);
//此时父类也被影响了
console.log(Father.prototype) //{dance: ƒ, sing: ƒ, constructor: ƒ}
解决方法2:子类的原型指向父类的实例,这样就可以顺着原型链共享父类的方法了。并且为子类添加原型方法的时候,不会影响父类。
function Father(name) {
this.name = name;
}
Father.prototype.dance = function () {
console.log('I am dancing');
};
function Son(name, age) {
Father.call(this, name);
this.age = age;
}
Son.prototype = new Father();
Son.prototype.sing = function () {
console.log('I am singing');
};
let son = new Son('小红', 100);
console.log(Father.prototype) //{dance: ƒ, constructor: ƒ}
五.面试题分享
A.面试题1
Object.prototype.__proto__ //null
Function.prototype.__proto__ //Object.prototype
Object.__proto__ //Function.prototype
讲解: 这里涉及到Function的原型问题,附一张图,这图是一个面试官发给我的,我也不知道原作者在哪里~
B.面试题2
给大家分享那道我被卡住的面试题,希望大家在学习完知识后,可以回顾一下。
按照如下要求实现Person 和 Student 对象
a)Student 继承Person
b)Person 包含一个实例变量 name, 包含一个方法 printName
c)Student 包含一个实例变量 score, 包含一个实例方法printScore
d)所有Person和Student对象之间共享一个方法
es6类写法
class Person {
constructor(name) {
this.name = name;
}
printName() {
console.log('This is printName');
}
commonMethods(){
console.log('我是共享方法');
}
}
class Student extends Person {
constructor(name, score) {
super(name);
this.score = score;
}
printScore() {
console.log('This is printScore');
}
}
let stu = new Student('小红');
let person = new Person('小紫');
console.log(stu.commonMethods===person.commonMethods);//true
原生写法
function Person (name){
this.name = name;
this.printName=function() {
console.log('This is printName');
};
}
Person.prototype.commonMethods=function(){
console.log('我是共享方法');
};
function Student(name, score) {
Person.call(this,name);
this.score = score;
this.printScore=function() {
console.log('This is printScore');
}
}
Student.prototype = new Person();
let person = new Person('小紫',80);
let stu = new Student('小红',100);
console.log(stu.printName===person.printName);//false
console.log(stu.commonMethods===person.commonMethods);//true