关于javascript:这些js原型及原型链面试题你能做对几道

4次阅读

共计 7923 个字符,预计需要花费 20 分钟才能阅读完成。

一、前言

在面试过程中,频频被原型相干常识问住,每次答复都支支吾吾。起初有家十分心仪的公司,在二面时,果不其然,又问原型了!

我痛下决心用了两天工夫钻研了下原型,弄明确后发现世界都亮堂了,原来这么简略 ~

有些了解还比拟肤浅,随着工夫的推移和了解的深刻,当前还会补充。如果大家发现我了解的有问题,欢送大家在评论中斧正。话不多说,切入正题。

二、构造函数

讲原型则离不开构造函数,让咱们先来意识下构造函数。

2.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.2.3 每个实例的办法是共享的吗?

这要看咱们如何定义该办法了,分为两种状况。

办法 1:在构造函数上间接定义方法(不共享)
    function Star() {this.sing = function () {console.log('我爱唱歌');
        }
    }
    let stu1 = new Star();
    let stu2 = new Star();
    stu1.sing();// 我爱唱歌
    stu2.sing();// 我爱唱歌
    console.log(stu1.sing === stu2.sing);//false

很显著,stu1 和 stu2 指向的不是一个中央。
所以 在构造函数上通过 this 来增加办法的形式来生成实例,每次生成实例,都是新开拓一个内存空间存办法。这样会导致内存的极大节约,从而影响性能。

办法 2:通过原型增加办法(共享)

构造函数通过原型调配的函数,是所有对象共享的。

    function Star(name) {this.name = name;}
    Star.prototype.sing = function () {console.log('我爱唱歌', this.name);
    };
    let stu1 = new Star('小红');
    let stu2 = new Star('小蓝');
    stu1.sing();// 我爱唱歌 小红
    stu2.sing();// 我爱唱歌 小蓝
    console.log(stu1.sing === stu2.sing);//true
2.2.4 实例的属性为根本类型是,它们是共享的吗?

属性存储的是如果存储的是根本类型,不存在共享问题,是否雷同要看值内容。

    let stu1 = new Star('小红');
    let stu2 = new Star('小红');
    console.log(stu1.name === stu2.name);//true

    let stu1 = new Star('小红');
    let stu2 = new Star('小蓝');
    console.log(stu1.name === stu2.name);//false
2.2.5 定义构造函数的规定

公共属性定义到构造函数外面,公共办法咱们放到原型对象身上。

三、原型

后面咱们在 实例化 和 实例共享方法 时,都提到了原型。那么当初聊聊这个神秘的原型到底是什么?

3.1 什么是原型?

Father.prototype 就是原型,它是一个对象,咱们也称它为原型对象。

3.2 原型的作用是什么?

原型的作用,就是共享方法。
咱们通过 Father.prototype.method 能够共享方法,不会反馈开拓空间存储办法。

3.3 原型中 this 的指向是什么?

原型中 this 的指向是实例。

四、原型链

4.1 什么是原型链?

原型与原型层层相链接的过程即为原型链。

4.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

4.3 原型链图

4.4 原型查找形式

例如:查找 obj 的 dance 办法

    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)如果再没有,则会报错。

4.5 原型的结构器

原型的结构器指向构造函数。

    function Star(name) {this.name = name;}
    let obj = new Star('小红');
    console.log(Star.prototype.constructor === Star);//true
    console.log(obj.__proto__.constructor === Star); //true
4.5.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

办法 2:Star.prototype = {}给原型从新赋值,此时会失落结构器,咱们须要手动定义结构器,指回构造函数自身

    function Star(name) {this.name = name;}
    Star.prototype = {dance: function () {console.log(this.name);
        }
    };
    let obj = new Star('小红');
    console.log(obj.__proto__); //{dance: ƒ}
    console.log(obj.__proto__.constructor); //  ƒ Object() { [native code] }
    Star.prototype.constructor = Star;
4.5.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: ƒ}

七、类

什么是类?

类的实质还是一个函数,类就是构造函数的另一种写法。

function Star(){}
console.log(typeof Star); //function

class Star {}
console.log(typeof Star); //function

ES6 中类没有变量晋升

通过构造函数创立实例,是能够变量晋升的。
es6 中的类,必须先有类,才能够实例化。

类的所有办法都定义在类的 prototype 属性下面

让咱们来测试一下。

    class Father{constructor(name){this.name = name;}
        sing(){return this.name;}
    }
    let red = new Father('小红');
    let green = new Father('小绿');
    console.log(red.sing === green.sing); //true

向类中增加办法

通过 Object.assign,在原型上追加办法。

    class Father{constructor(name){this.name = name;}
        sing(){return this.name;}
    }
    // 在原型上追加办法
    Object.assign(Father.prototype,{dance(){return '我爱跳舞';}
    });
    let red = new Father('小红');
    let green = new Father('小绿');
    console.log(red.dance());// 我爱跳舞
    console.log(red.dance === green.dance); //true

constructor 办法

constructor 办法是类的默认办法,通过 new 命令生成对象实例时,主动调用该办法。一个类必须有 constructor 办法,如果没有显式定义,一个空的 constructor 办法会被默认增加。

八、继承 – ES6 办法

    class Father {constructor(name){this.name = name;}
        dance(){return '我在跳舞';}
    }
    class Son extends Father{constructor(name,score){super(name);
            this.score = score;
        }
        sing(){return this.name +','+this.dance();
        }
    }
    let obj = new Son('小红',100);

九、类和构造函数的区别

(1) 类必须应用 new 调用,否则会报错。这是它跟一般构造函数的一个次要区别,后者不必 new 也能够执行。

(2) 类的所有实例共享一个原型对象。

(3) 类的外部,默认就是严格模式,所以不须要应用 use strict 指定运行模式。

十、总结

构造函数特点:

1. 构造函数有原型对象 prototype。

2. 构造函数原型对象 prototype 外面有 constructor,指向构造函数自身。

3. 构造函数能够通过原型对象增加办法。

4. 构造函数创立的实例对象有__proto__原型,指向构造函数的原型对象。

类:

1.class 实质还是 function

2. 类的所有办法都定义在类的 prototype 属性上

3. 类创立的实例,外面也有__proto__指向类的 prototype 原型对象

4. 新的 class 写法,只是让对象原型的写法更加清晰,更像面向对象编程的语法而已。

5.ES6 的类其实就是语法糖。

十一、什么是语法糖

什么是语法糖?加糖后的代码性能与加糖前保持一致,糖在不扭转其所在位置的语法结构的前提下,实现了运行时的等价。

语法糖没有扭转语言性能,但减少了程序员的可读性。

十二、面试题分享

面试题 1

Object.prototype.__proto__    //null
Function.prototype.__proto__  //Object.prototype
Object.__proto__              //Function.prototype

解说:
这里波及到 Function 的原型问题,附一张图,这图是一个面试官发给我的,我也不晓得原作者在哪里~

面试题 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
正文完
 0