看完这篇文章,彻底了解 “原型” & “this”

code __proto__

function Test () {}

Test.prototype.name = ‘test’

var test01 = new Test()

var test02 = new Test()

test01.proto === test02.proto    // true

// ----------------------- 实例之后的对象调用__proto__指针指向的 等于被实例的构造函数的prototype!

// test01.proto = Test.prototype  // true

这时候,是不是已经恍然大悟了!原来通过prototype 定义的属性,再被多个实例化之后,引用的地址是同一个!并且 proto 就是我们上面使用的prototype 属性的马甲啊!就是说,我们在构造函数中使用prototype 定义的属性,都会被 proto 指针引用!

好了,这个时候,可以整一段比较晦涩的总结了:每个对象都有一个 proto 的属性,指向该对象的原型。实例后通过对 proto 属性的访问 去对 prototype对象进行访问;原型链是由原型对象组成的,每个对象都有__proto__属性,指向创建该对象的构造函数的原型 ,然后通过__proto__属性将对象链接起来,组成一个原型链,用来实现继承和共享属性!

理清楚以上关系后,可以想一下 通过prototype 定义的属性作用就仅仅如此么?接着看一段代码:

code prototype

function Test () {}

Test.prototype.name = ‘test’

var test01 = new Test()

console.log( test01.name ) // “test”

Test.prototype.name = 'no test ’

console.log( test01.name ) // “no test”

看到了什么?原来 prototype 可以在实例之后,再进行更改呀!

就是说,通过构造函数去改变name 的值,实例化之后的对象,引用的属性值也会跟着变。太强大了!

再来看看 constructor :

code constructor

function User(name, age) {

this.name = name; // 这里面的this,就代表了即将生成的那个对象 ,并且绑定传参

this.age = age;

}

User.prototype.greet = function () {

console.log(‘你好, 我是’ + this.name + ‘,我’ + this.age + ‘岁’);

}

var lisi = new User(‘李四’, 22);

// 再次构造

var zhangsan = new lisi.constructor(‘张三’, 20) // 使用constructor来实例化!!!

new lisi.constructor() === new User()  // true

console.log(zhangsan)

/*

User {}

name:‘张三’

age = 20

proto

greet:f()

constructor : f User (name, age)

proto:Object

*/

发现了吗?就算我只能知道实例后的对象,但是我可以通过 proto 去找到这个实例对象的构造函数 constructor ,我再通过这个构造函数再去实例对象。(var zhangsan = new lisi.constructor(‘张三’, 20))与我直接var zhangsan = new User(‘张三’, 20)。完全一样。真的很强大!

好了,讲到这,proto & prototype 也就说完了,接下来再说说 原生对象的原型

原型-原生对象的原型


前面,知道了原型的概念,那就趁热打铁,接着看看原生对象的原型。

先看一段代码:

code 原生对象

var a ={}

console.log(a)

/*

{}

proto

greet:f()

constructor : f Object()

*/

可以看到,我们var 了一个新对象之后,没有定义任何属性,但是也能看到他的构造函数:Object()。也就是说:var a ={} === var a = new Object(),两者没有任何区别。举个例子:

code 原生对象

var a ={}

var b = new Object()

console.log(a.constructor === b.constructor ) // true

可以看到,构造函数完全一样。

那么这个时候,可能会有同学想问,怎么去创造一个干净的对象呢?里面没有任何集成的属性等。

当然也是可以的。接着看:

code 原生对象

var a = new Object.create(null) // 创建函数必须传参,一个对象或者是 null ,否则会报错!

console.log( a )

/*

no prototies

*/

可以看到,通过 Object.create() 创建的对象,属性为空。这个时候,肯定会有同学有疑问,你这传的参数是 null,那当然什么都没有了,你传个对象试试。哈哈哈,确实,如果传对象的话,那就是定义自己所自带的原型了。举个例子:

code 原生对象

var a = new Object.create({name:juejin,des:“666”}) // 创建函数必须传参,一个对象或者是 null ,否则会报错!

console.log( a )

/*

{}

proto

name:juejin

des:“666”

proto

constructor : f Object()

*/

可以看到,再Object.create() 中传入对象的属性,是放在第一层的 proto 下面的,也就是中,这是你创建的这个原型对象的继承属性,意味着,可以根据自身的业务需求,来定义自己的原型对象!

多级继承链


好了,上面已经详细的讲解了原型链,构造函数,那么就试着来实现一个继承链。看下面代码:

code 继承链 从祖父 到爷爷 到爸爸 到自己

// Animal --> Mammal --> Person --> me

// Animal

function Animal(color, weight) {

this.color = color;

this.weight = weight;

}

Animal.prototype.eat = function () {

console.log(‘吃饭’);

}

Animal.prototype.sleep = function () {

console.log(‘睡觉’);

}

//  Mammal

function Mammal(color, weight) {

Animal.call(this, color, weight); //绑定 this 这个下面讲

}

Mammal.prototype = Object.create(Animal.prototype);

Mammal.prototype.constructor = Mammal;

Mammal.prototype.suckle = function () {

console.log(‘喝牛奶’);

}

//  Person

function Person(color, weight) {

Mammal.call(this, color, weight);

}

Person.prototype = Object.create(Mammal.prototype);

Person.prototype.constructor = Person;

Person.prototype.lie = function () {

console.log(‘你是个骗子’);

}

// 实例

var zhangsan = new Person(‘brown’, 100);

var lisi = new Person(‘brown’, 80);

console.log(‘zhangsan:’, zhangsan);

console.log(‘lisi:’, lisi);

上面的代码中,实现了三级继承。其中,使用了我们上面讲到的 prototype 以及 Object.create() 。

code

function Animal(color, weight) {

this.color = color;

this.weight = weight;

}

Animal.prototype.eat = function () {

console.log(‘吃饭’);

}

往祖父类中写入继承属性,eat 供爷爷辈来继承这个吃的属性。

code

//  Mammal

function Mammal(color, weight) {

Animal.call(this, color, weight); //绑定 this 这个下面讲

}

Mammal.prototype = Object.create(Animal.prototype);

Mammal.prototype.constructor = Mammal;

Mammal.prototype.suckle = function () {

console.log(‘喝牛奶’);

}

同时,爷爷辈的属性,需要继承祖父辈的其他属性,因为上面有讲到:prototype 是继承属性,也可以称之为隐性属性。那么 color, weight 这些显性属性怎么给他继承过来呢?

这个时候就用上了上面的 Mammal.prototype = Object.create(Animal.prototype); 这就是利用 Object.create() 来将祖父的其他显性属性,全部继承到爷爷辈。并且再写进爷爷辈的 prototype 中,方便再往下给爸爸继承。

这样一级一级的绑定,构建,就实现了所谓的 多级继承 了。

当然细心的同学又发现了一个点:

code

//  Mammal

function Mammal(color, weight) {

Animal.call(this, color, weight); //绑定 this

}

为什么这边的爷爷辈的构造器里面为什么要 call this 呢? ,这边就先卖个关子,下面this那段会讲到!嘿嘿~

原型总结


好了,讲了这么多,终于说完了原型链。其实一图胜千言。

引用上面的一句话:每个对象都有一个 proto 的属性,指向该对象的原型。实例后通过对 proto 属性的访问 去对 prototype对象进行访问;原型链是由原型对象组成的,每个对象都有__proto__属性,指向创建该对象的构造函数的原型 ,然后通过__proto__属性将对象链接起来,组成一个原型链,用来实现继承和共享属性!

说到这,原型链也就说完了,接下来再啃一块硬骨头:this

this

====

其实说到 this,大家都有这样的一个感觉,就是一看就会,一用就乱。那么这个this 到底是个啥?能不能给它整明白?别急,

先来看一段代码:

code

var User = {

fname:‘三’,

lname:‘张’,

fullname:function(){

return User.lname + User.fname

}

}

console.log(User.fullname) // “张三”

这段代码是去获取 User 对象下的全名,可以看到是没什么问题。那么这个时候,需要给这个对象换成person对象,会发生什么呢?

code

var Person = {

fname:‘三’,

lname:‘张’,

fullname:function(){

return User.lname + User.fname

}

}

console.log(Person.fullname) // User is not defined

看到了什么,找不到这个 User,这是为什么呢?很明显,是因为我们再return 中,返回的还是 User 这个对象,但是这个时候,我已经将原来的 User 改成 Person 了。所以,如果这段代码想生效,必须也要将 return 中的 User 对象 改成 Person 对象。

麻不麻烦?可重用性也太低了。那么这个时候,this 就派上用场了。接着看:

code

var Person = {

fname:‘三’,

lname:‘张’,

fullname:function(){

return this.lname + this.fname

}

}

console.log(Person.fullname) // “张三”

这时候,就能看到,我对象名改成了Person,是一样可以拿到这个对象下的 fullname

是不是有同学会问了,这是为什么?其实这个时候,这里面的this,就指向了这个fullname 的 fnc 外的Person对象了。是不是觉得说的有点干,那我们就来看看:

code

var Person = {

fname:‘三’,

lname:‘张’,

fullname:function(){

console.log(this) // 在哪边引用this,就在哪边看!

return this.lname + this.fname

}

}

/*

fname:‘三’

lname:‘张’

fullname:f()

proto

constructor : f Object()

*/

这样看,是不是十分清晰明了。其实也就是说,我在 fullname 这个方法中使用的 this 就是指向了,我当前这个 function 代码块的上一级。

看到这,是不是感觉明白了?再来:

code

var Person = {

fname:‘三’,

lname:‘张’,

fullname:function(){

return this.lname + this.fname

}

}

var  getfullname = Person.fullname // 将Person对象中的fullname 方法,给到新定义的参数使用

console.log(getfullname()) // NAN

这是什么?没拿到 张三 ?这是为啥?

到这里是不是一下子又懵了?这个 this 到底有多少幺蛾子。打印出来看看,这个时候的 this 到底是什么:

code

var Person = {

fname:‘三’,

lname:‘张’,

fullname:function(){

console.log(this)

return this.lname + this.fname

}

}

var  getfullname = Person.fullname // 将Person对象中的fullname 方法,给到新定义的参数使用

console.log(getfullname()) // window:{},NAN

看到什么了?这个 this 竟然指向了window,全局变量。这是咋回事?这就是 this 坑的地方,我上面说到:this 就是指向了,我当前这个 function 代码块的上一级。其实这句话,在这边就直接错了。因为this引用没变。只是我的调用方式变了。

所以这个时候,这句话要重新描述,谨记:this 并不取决于它所在的位置,而是取决于它所在的function是怎么被调用的!!!

而上面 console.log(Person.fullname) // “张三” 可以打印出结果,就是fullname的这个方法,直接被它的父级调用了,也就是说这个时候的 this 是指向的 Person

而如果指定调用这个 this 的,并不是直接父级,那么再非严格模式下,指向的就是全局 window,而在严格模式下则是 undefined

再来 如果 this 再构造函数中被调用,会是怎么样?看下面一段代码 :

code

function User (){

console.log(this)

}

User () // undefined

new User () // User {}

这个时候,可以看到,如果 this 是放在构造函数中,被直接调用 User (),那么这个时候的 this 就是 undefined 。因为 this 所在的 function 并没有作为一个方法被调用。

而 如果是通过 new 的方式被调用的,那么这个时候, this 所在的 function 就被调用了,并且指向的就是被调用的 User {} 。还记得我们上面说的,js 本身的构造函数机制吗?再来复习一下:

code 创建对象 "

function User(name, age) {

this.name = name; // 这里面的this,就代表了即将生成的那个对象 ,并且绑定传参

this.age = age;

}

就是说:构造函数中的 this ,就是指向即将实例化的那个对象。谨记!

所以 总结一下 this 的三种场景:

1. 如果this 是 在一个函数中,并且被用作方法来叫,那么这个时候的 this 就指向了父级对象;

  1. 如果this 是在匿名函数,或者全局环境的函数中,那么这个时候的 this 就是;undefined;

3. 如果this 是在构造函数中,那么这个时候的 this 就指向了即将生成的那个对象

好了,既然区分了 this 的使用场景之后,那么它的强大之处是什么呢?举个例子:

code 动态绑定 this

function introduction() {

console.log(‘你好, 我是’ + this.name);

}

var zhangsan = {

name: ‘张三’,

}

var lisi = {

name: ‘李四’,

}

zhangsan.introduction = introduction;

lisi.introduction = introduction;

zhangsan.introduction(); //  你好,我是张三

lisi.introduction();  //  你好,我是李四

上面可以看到,定义了一个方法,这个方法中使用了 this.name ,但是这个时候,并不知道,这个方法中的 this 到底指向的是谁,而是等待着谁来调用它。回忆一下上面说的那句话:this 并不取决于它所在的位置,而是取决于它所在的function是怎么被调用的!!!

而这个时候,定义了 张三 和 李四 两个对象,这两个对象,分别将定义的 introduction 赋值到本身的对象下面,也就是说,这个时候, 张三 和 李四 两个对象,都拥有了 introduction 这个方法,并且调用了。所以,这个时候的 function introduction() 已经拥有了被调用的对象,所以其中的 this.name 也就分别指向了这两个对象的中name。

好,以上就是将 this 的默认指向讲完了。但是是不是有个问题,还没解决?

那就是我们之前在说 多级继承 的时候,有个 call this 。这个卖的关子 还没说呢?那接下来就讲讲。关于 this 改变它的默认指向,绑定一个我想要绑定的环境,行不行?

bind & apply & call

好了,这一段,就接着上面的讲,这里会讲到关于 this 的三种绑定方法。先来看代码:

code 动态绑定 this

function introduction() {

console.log(‘你好, 我是’ + this.name);

}

introduction() // 你好, 我是 undefined

这个结果相信大家不会陌生,因为就是上面讲的第二种情况:2. 如果this 是在匿名函数,或者全局环境的函数中,那么这个时候的 this 就是;undefined

这里普及一个知识:introduction() === introduction.call() 只是前者是后者的简写!并且call()中的第一个传参可以指定这个函数中的 this 指向谁!

好了,知道这个知识点,再看下面的代码:

code 动态绑定 this

function introduction() {

console.log(‘你好, 我是’ + this.name);

}

var zhangsan = {

name:‘张三’

}

introduction.call(zhangsan) // 你好, 我是 张三

看完是不是一目了然,这个call()里面传的参数,指向了 zhangsan 这个对象。那这不就是给这个 introduction 方法指定了调用的父级了吗?this 也就指向给调用这个方法的 zhangsan 了呀!

说到这是不是就能清楚的知道,这个跟上面 在对象中,来绑定这个方法,来关联父级调用关系,是一样的。一个是对象引用方法,这个就是方法绑定对象呀!
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

核心竞争力,怎么才能提高呢?

成年人想要改变生活,逆转状态?那就开始学习吧~

万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。

为了帮助大家更好更高效的准备面试,特别整理了《前端工程师面试手册》电子稿文件。

前端面试题汇总

JavaScript

性能

linux

前端资料汇总

完整版PDF资料免费分享,只需你点赞支持,动动手指点击此处就可领取了

前端工程师岗位缺口一直很大,符合岗位要求的人越来越少,所以学习前端的小伙伴要注意了,一定要把技能学到扎实,做有含金量的项目,这样在找工作的时候无论遇到什么情况,问题都不会大。

目、讲解视频,并且会持续更新!**

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

核心竞争力,怎么才能提高呢?

成年人想要改变生活,逆转状态?那就开始学习吧~

万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。

为了帮助大家更好更高效的准备面试,特别整理了《前端工程师面试手册》电子稿文件。

前端面试题汇总

JavaScript

性能

linux

前端资料汇总

完整版PDF资料免费分享,只需你点赞支持,动动手指点击此处就可领取了

前端工程师岗位缺口一直很大,符合岗位要求的人越来越少,所以学习前端的小伙伴要注意了,一定要把技能学到扎实,做有含金量的项目,这样在找工作的时候无论遇到什么情况,问题都不会大。

  • 8
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值