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 就指向了父级对象;
- 如果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前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
![](https://img-blog.csdnimg.cn/img_convert/5c8c053aae85a9303d10853018638e8a.jpeg)
核心竞争力,怎么才能提高呢?
成年人想要改变生活,逆转状态?那就开始学习吧~
万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。
为了帮助大家更好更高效的准备面试,特别整理了《前端工程师面试手册》电子稿文件。
前端面试题汇总
JavaScript
性能
linux
前端资料汇总
完整版PDF资料免费分享,只需你点赞支持,动动手指点击此处就可领取了。
前端工程师岗位缺口一直很大,符合岗位要求的人越来越少,所以学习前端的小伙伴要注意了,一定要把技能学到扎实,做有含金量的项目,这样在找工作的时候无论遇到什么情况,问题都不会大。
目、讲解视频,并且会持续更新!**
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
![](https://img-blog.csdnimg.cn/img_convert/5c8c053aae85a9303d10853018638e8a.jpeg)
核心竞争力,怎么才能提高呢?
成年人想要改变生活,逆转状态?那就开始学习吧~
万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。
为了帮助大家更好更高效的准备面试,特别整理了《前端工程师面试手册》电子稿文件。
前端面试题汇总
JavaScript
性能
linux
前端资料汇总
完整版PDF资料免费分享,只需你点赞支持,动动手指点击此处就可领取了。
前端工程师岗位缺口一直很大,符合岗位要求的人越来越少,所以学习前端的小伙伴要注意了,一定要把技能学到扎实,做有含金量的项目,这样在找工作的时候无论遇到什么情况,问题都不会大。