秒杀原型面试题?看这一篇就够啦!


theme: cyanosis

一,原型

在JavaScript中,原型(Prototype)是一个核心概念,它构成了JavaScript面向对象编程的基础 大家还记得创建对象的几种方法吗?有3种方法对不对

1.创造对象字面量 2.调用自定义的构造函数 3.通过new一个函数(调用系统自带的函数)

在js这门语言中是对象就能具有属性,这与许多其他编程语言中使用的基于类的(Class-based)模型有所不同。在基于原型的对象模型中,对象从另一个对象(称为“原型”)继承属性和方法,而不是从类继承。 那么我们创造了对象之后,对象身上有哪些属性呢?举个例子,我们打开百度的后台中的控制台,以构造一个自定义的函数function Person(){}为例我们可以查找到他的身上具有的一些方法,例如,name,length,对了你还记得对象的作用域[[scope]]吗,我们不能直接访问或操作 [[Scope]],因为它是一个内部机制。接下来我们需要引入今天的学习内容----原型prototype这个概念,我们通过查询这个函数的这个属性名,发现确实存在一个集合{}

二,原型的定义

定义:每个JavaScript对象都有一个与之关联的原型对象。这个原型对象是一个包含可以由其他对象继承的属性和方法的对象。换句话说,原型是对象的属性和方法的集合,它定义了对象的基本行为。 通俗点来讲到底他就是是函数上的一个属性,他定义了通过构造函数制造出的对象的公共祖先,这样使得这个对象身上具有隐式属性。

三,通过构造函数的隐式具有

Person.prototype.lastName = 'zhu'
Person.prototype.say = function () {
    console.log('Hello');
}
function Person() {
    this.name = 'tao'
}

let p1 = new Person()
p1.say()

我们通过prototype可以将lastName和say隐式的添加到函数中。在上面函数中我们通过打印p1只能得到他的显示属性name:tao,但是通过p1.say(),p1.lastName查询他身上的属性时,会发现我们通过prototype给他添加上的隐式属性。 这个方法有什么作用呢?在项目中的公共属性太多时 ,项目需要批量生产时,我们就可以把他们的公共属性隐藏起来,打个比方

function Car(color, owner) {
this.name = "su7";
this.lang = 5000;
this.height = 1400;
this.color = color;
this.owner = owner;

}

let zhutao = new Car(“black”, “zhutao”);
let xu = new Car(“red”, “xu”);

console.log(zhutao, xu);

因为构造函数New出来的对象,会隐式继承构造函数的原型上的属性,在这里,我们看到对象zhutyao和xu在函数Car中只有color和owner不同,我们保留这两个属性名,将其他属性隐藏

Car.prototype = {
    name: "su7",
    lang: 5000,
    height: 1400
}
//或者
//Car.prototype.name = "su7";
//Car.prototype.lang = 5000;
//Car.prototype.height = 1400;

function Car(color, owner) {

this.color = color;
this.owner = owner;

}

let zhutao = new Car(“black”, “zhutao”);
let xu = new Car(“red”, “xu”);

console.log(zhutao, xu);

这样打印出来的结果一样,但是隐藏属性后节省了时间提升了效率。那么接下来需要注意的点来了,如果我需要改动显示属性我依然可以直接通过构造一个函数后,通过新函数,用对象的方式直接修改增加属性,可是隐式具有的属性是无法被更改的

Car.prototype.product = 'xiaomi';

function Car() {
this.name = ‘su7’;
}

let car = new Car()

car.name = ‘保时捷’
car.product = ‘Huawei’

console.log(car);

你看上面这块代码,我可以直接修改显示的name属性,可是当我想修改隐式中的product时,反而是又添加了一个product属性。在这里,我们要熟悉关于原型的定义规则

四,隐式原型与显示原型:

隐式原型:在JavaScript中,每个对象都有一个特殊的内置属性[[Prototype]](通常被称为“隐式原型”),该属性指向一个对象,这个对象就是该对象的原型。

显示原型:每个函数对象(包括构造函数)都有一个prototype属性(通常被称为“显示原型”),这个属性也是一个对象,它是该函数创建的所有实例的原型。

五,原型的规则

  1. __proto__代表对象的原型,prototype代表函数的原型; 对象的隐式原型 === 创建他的构造函数的显示原型(那个new函数的原型)

即car.proto === Car.prototype (构造函数的原型等于所有实例对象的原型) 2. js引擎在查找属性时,会先查找对象显示具有的属性,如果没有,就会查找他的隐式原型__proto__,如果隐式原型也没有,就继续往上找,直到找到Object.prototype为止。Object的隐式原型是null 3. 实例对象可以修改显示继承的属性,但是不能修改构造函数隐式继承到的属性(原型上的属性)

  1. 实例对象不能给原型新增属性

  2. 实例对象不能给原型删除属性

当你去访问实例对象身上的constructor时,(consturctor是用来指明记录这个对象是谁创建的)我们可以通过他清楚的知道这个对象是由谁创建的,甚至可以修改constructor的指向,但是这通常不是一个好主意,因为这可能会破坏JavaScript的原型链和继承机制。

六,通过原型链的隐式继承

我们已经知道了JS引擎在查找时运行的规则--1.最先查找其对象上的显示原型2.再查找他的隐式原型即原函数的显示原型3.再找不到就会向上继续查找。这种一直向上的查找方式和作用域向外查询变量的方式一样会会形成链条,不同的是作用域链是一种类集合,原型链是一种链状结构。

七,原型链

由此我们可以总结: 当一个对象需要访问某个属性或方法时,JavaScript 会首先查看该对象自身是否拥有这个属性或方法。如果有,就直接使用;如果没有,JavaScript 就会去查找该对象的原型对象(即 __proto__ 属性指向的对象),看原型对象是否拥有该属性或方法。如果原型对象也没有,那么就会继续查找原型对象的原型对象,这样一直查找下去,就形成了一个链状结构,这就是原型链。

由此我们知道了属性继承的两者方式:借用构造函数继承,原型链继承。

我们通过一个例子来完全掌握通过原型链的属性继承,

GrandFather.prototype.say = function () {
console.log('haha');
}
function GrandFather() {
    this.age = 60
    this.like = 'drink'
}

Fathere.prototype = new GrandFather()
function Father(){
this.age = 40
this.fortune = {
card:‘visa’
}
}

Son.prototype = new Father()
function Son(){
this.age = 18
}
let son = new Son()
console.log(son.age); //18
console.log(son.fortune);//card:‘visa’
console.log(son.like); //‘drink’
console.log(son.say); //‘haha’

如上代码我们的四个打印结果来看,通过.prototype在son中查找的属性都能一一往上继承,这就相当于那么多的属性其实都放进了Son这个函数里面,只是我们看不见而已。

需要注意的是

原始类型(如 NumberStringBooleanNullUndefined 和 Symbol)在JavaScript中是不可变的简单数据类型。它们表示单个的、简单的值,而不是对象,因此它们没有自己的属性和方法。虽然JavaScript中的对象是通过原型链来继承属性和方法的,但原始类型并不是对象,因此它们没有原型链。但这意味着你不能通过原型来给原始类型添加属性。

八,内置构造函数的内置属性

当我们在嘎嘎敲代码的时候,不乏频繁的调用过内置函数的内置属性,啥是内置构造函数?我一举例你就明白----Array``String``Number``Math.....这些内置对象都天生具有许多可以被调用的内置属性和方法,使得开发者可以方便地进行各种复杂的编程任务。我们可以查询到他们的全部内置属性,以arr为例:

我们来解答一个问题

var arr = []
arr.push(1)
console.log(arr);

为什么arr能够使用push方法?不就是因为在arr的隐式原型中内置了这个方法吗,底层设计师通过Array.prototype.push 把push方法放进了arr的原型中,实际上的方法如下:

Array.prototype.push = function(){}
function Array(){}

他们的隐式属性在他们被构造时就被打造好啦,我们要清楚且熟悉的掌握一些常用的方法,往后我们将专门开一篇来清点中介他们常用的方法(开新坑)。

九,网易面试:所有对象都有隐式原型吗?

我们在上文提到过,所有对象都具有隐式原型内置属性。然而事实真的如此吗? 我们在此必须纠正!

在 JavaScript 中,每个对象(除了 null 和原始值)通常都有一个指向其原型对象的内部链接,这个链接就是 [[Prototype]]。这个原型对象可以是任何对象,但通常是构造函数的 prototype 属性。然而,Object.create(null) 创建了一个“无原型”对象,这意味着这个对象没有继承自任何其他对象,因此它不能访问任何定义在原型链上的属性和方法。

object.creat()

当调用object中的方法creatobject.creat()时,在后台控制台中会产生一个空集{},这说明他创建了一个新对象。当往()中输入值时,例如输入下题中的a

let a = {
    name:'Tom'
}
let obj = Object.create(a)

他会让这个空对象隐式继承 a对象里的的属性。但是如果在()中输入null时,新产生的这个对象不具有原型,这是唯一的反例。


这是一个从 https://juejin.cn/post/7368469208647335955 下的原始话题分离的讨论话题