从this 到call 到new 到原型链
1、总结特点:
2、对于call apply这一块
括号里面的第一个参数会反客为主,因为最前面的参数是函数,函数只有被调用的份儿
var list1 = [0,1,2];
var list2 = [3,4,5];
[].push.apply(list1,list2);
console.log(list1);// >>> [0,1,2,3,4,5]
3、new的时候发生了什么?
new
关键字会进行如下的操作:
- 创建一个空的简单JavaScript对象(即
{}
); - 为步骤1新创建的对象添加属性__proto__,将该属性链接至构造函数的原型对象 ;( ....__proto==constructor.prototype(那么杰哥原型链那些,就是链接到了属性共享的方面~)
- 将步骤1新创建的对象作为
this
的上下文 ;(实际上就是call改变那句话,构造函数.call(实例)这样就相当于实例.构造函数,使得函数里的this指向实例====》实际上【就是对象作为了this上下文】 - 如果该函数没有返回对象,则返回
this
。
基本就是这四步,但有的地方会写错,比如。。一定要避雷这篇 https://juejin.cn/post/6844904003545858056 (而甚至,听得课里面的老师也看的这篇导致这里有一部分直接误人子弟,讲的错了!!!!这里面没提原型链,这是理解上的误区,因为call这个马上就执行了,没必要再拆成两个(所以说看网上不权威的东西危害是多么大!一定要多看几篇综述一下!)】
为什么会有这种认知呢,,https://www.zhihu.com/question/36440948 这里29赞这个说了,js(三)里是这么写的,但实际上少了原型链这部分,所以不要生搬硬套!
4、【继续深究(思考之后得到的)】
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this.a); //12
}
}
}
o.b.fn();
①emmm。return fn()的话本质也是先调用fn() 再看fn的返回值是啥返回那个值,还是没有做到xxx.fn
② 如果是在函数内先扒拉扒拉。。。
好吧,函数要写语句的,不能写成员变量,所以这个问题先无解,
但确实是可以理解为嵌套函数不继承的。~~
toString()
方法都是重写了的,没有重写的那些当然就返回一样的结果啦,为了统一就用食物链顶端的Object来做法了。5、原型链系列。。。 因为new的时候也有原型链就去复习了一下,不过明天再写吧
__proto__
通常称为隐式原型,prototype
通常称为显式原型,那我们可以说一个对象的隐式原型指向了该对象的构造函数的显式原型。那么我们在显式原型上定义的属性方法,通过隐式原型传递给了构造函数的实例。这样一来实例就能很容易的访问到构造函数原型上的方法和属性了。__proto__
、 constructor
属性是对象所独有的;② prototype
属性是函数独有的;③ js中函数也是对象的一种,那么函数同样也有属性
__proto__
、 constructor
;var Parent = function(){
}
//定义一个函数,那它只是一个普通的函数,下面我们让这个函数变得不普通
var p1 = new Parent();
//这时这个Parent就不是普通的函数了,它现在是一个构造函数。因为通过new关键字调用了它
//创建了一个Parent构造函数的实例 p1
为了方便举例,我们在这模拟一个场景,父类比作师父,子类比作徒弟。师父收徒弟,
徒弟还可以收徒弟。徒弟可以得到师父传授的武功,然后徒弟再传给自己的徒弟。
师父想要传授给徒弟们的武功就放到“prototype”这个琅琊福地中。徒弟徒孙们就去这里学习武功。
prototype属性可以看成是一块特殊的存储空间,存储了供“徒弟”、“徒孙”们使用的方法和属性。
(只有函数才有的prototype)
(下面是null)
prototype是函数独有的属性,从图中可以看到它从一个函数指向另一个对象,代表这个对象是这个函数的原型对象,这个对象也是当前函数所创建的实例的原型对象。prototype
设计之初就是为了实现继承,让由特定函数创建的所有实例共享属性和方法,也可以说是让某一个构造函数实例化的所有对象可以找到公共的方法和属性。有了prototype
我们不需要为每一个实例创建重复的属性方法,而是将属性方法创建在构造函数的原型对象上(prototype)。那些不需要共享的才创建在构造函数中。
继续引用上面的代码,当我们想为通过Parent实例化的所有实例添加一个共享的属性时,
Parent.prototype.name = "我是原型属性,所有实例都可以读取到我";
这就是原型属性,当然你也可以添加原型方法。那问题来了,p1
怎么知道他的原型对象上有这个方法呢,往下看↓↓↓
(2)proto属性
__proto__属性相当于通往prototype(“琅琊福地”)唯一的路(指针)
让“徒弟”、“徒孙” 们找到自己“师父”、“师父的师父” 提供给自己的方法和属性
prototype篇章我们说到,Parent.prototype
上添加的属性和方法叫做原型属性和原型方法,该构造函数的实例都可以访问调用。那这个构造函数的原型对象上的属性和方法,怎么能和构造函数的实例联系在一起呢,就是通过__proto__
属性。每个对象都有__proto__
属性,该属性指向的就是该对象的原型对象。
p1.__proto__ === Parent.prototype; // true
__proto__
通常称为隐式原型,prototype
通常称为显式原型,那我们可以说一个对象的隐式原型指向了该对象的构造函数的显式原型。那么我们在显式原型上定义的属性方法,通过隐式原型传递给了构造函数的实例。这样一来实例就能很容易的访问到构造函数原型上的方法和属性了。
我们之前也说过__proto__
属性是对象(包括函数)独有的,那么Parent.prototype
也是对象,那它有隐式原型么?又指向谁?
Parent.prototype.__proto__ === Object.prototype; //true
可以看到,构造函数的原型对象上的隐式原型对象指向了Object的原型对象。那么Parent的原型对象就继承了Object的原型对象。由此我们可以验证一个结论,万物继承自Object.prototype。这也就是为什么我们可以实例化一个对象,并且可以调用该对象上没有的属性和方法了。如:
//我们并没有在Parent中定义任何方法属性,但是我们可以调用
p1.toString();//hasOwnProperty 等等的一些方法
我们可以调用很多我们没有定义的方法,这些方法是哪来的呢?现在引出原型链的概念,当我们调用p1.toString()
的时候,先在p1
对象本身寻找,没有找到则通过p1.__proto__
找到了原型对象Parent.prototype
,也没有找到,又通过Parent.prototype.__proto__
找到了上一层原型对象Object.prototype。在这一层找到了toString方法。返回该方法供p1
使用。
当然如果找到Object.prototype上也没找到,就在Object.prototype.__proto__
中寻找,但是Object.prototype.__proto__ === null
所以就返回undefined。这就是为什么当访问对象中一个不存在的属性时,返回undefined了。
(3)constructor属性
constructor属性是让“徒弟”、“徒孙” 们知道是谁创造了自己,这里可不是“师父”啊
而是自己的父母,父母创造了自己,父母又是由上一辈人创造的,……追溯到头就是Function() 【女娲】。

constructor是对象才有的属性,从图中看到它是从一个对象指向一个函数的。指向的函数就是该对象的构造函数。每个对象都有构造函数,好比我们上面的代码p1
就是一个对象,那p1
的构造函数是谁呢?我们打印一下。
console.log(p1.constructor); // ƒ Parent(){}
通过输出结果看到,很显然是Parent函数。我们有说过函数也是对象,那Parent函数是不是也有构造函数呢?显然是有的。再次打印下。
console.log(Parent.constructor); // ƒ Function() { [native code] }
通过输出看到Parent函数的构造函数是Function(),这点也不奇怪,因为我们每次定义函数其实都是调用了new Function(),下面两种效果是一样的。
var fn1 = new Function('msg','alert(msg)');
function fn1(msg){
alert(msg);
}
那么我们再回来看下,再次打印Function.constructor
console.log(Function.constructor); // ƒ Function() { [native code] }
可以看到Function函数的构造函数就是本身了,那我们也就可以说Function是所有函数的根构造函数。
到这里我们已经对constructor属性有了一个初步的认识,它的作用是从一个对象指向一个函数,这个函数就是该对象的构造函数。通过栗子我们可以看到,p1
的constructor
属性指向了Parent
,那么Parent
就是p1
的构造函数。同样Parent
的constructor
属性指向了Function
,那么Function
就是Parent
的构造函数,然后又验证了Function
就是根构造函数。


后续思考
既然Array就指代了Array(),那arr.__proto__呢?其实只是Array.prototype,又回到了起点,函数其实是很高的,这个prototype才是弟弟
补了继承再来新增
如果真的是一种简易的脚本语言(设计初衷),其实不需要有"继承"机制。但是,Javascript里面都是对象,必须有一种机制,将所有对象联系起来。所以,Brendan Eich最后还是设计了"继承"。
但是,他不打算引入"类"(class)的概念,因为一旦有了"类",Javascript就是一种完整的面向对象编程语言了,这好像有点太正式了,而且增加了初学者的入门难度。
(2)他把new命令引入了js,用来从原型对象生成一个实例对象。但是,Javascript没有"类",怎么来表示原型对象呢?
这时,他想到C++和Java使用new命令时,都会调用"类"的构造函数(constructor)。他就做了一个简化的设计,在Javascript语言中,new命令后面跟的不是类,而是构造函数。
『仔细想想,确实var c=new Array() 首先是array()有括号那么是函数 显然这不是一个对象来的。。』而
举例来说,现在有一个叫做DOG的构造函数,表示狗对象的原型。
function DOG(name){
this.name = name;
}
对这个构造函数使用new,就会生成一个狗对象的实例。
var dogA = new DOG('大毛');
alert(dogA.name); // 大毛
注意构造函数中的this关键字,它就代表了新创建的实例对象。
(3)但是,单单这样做还是有个问题,那就是无法共享属性和方法
比如,在DOG对象的构造函数中,设置一个实例对象的共有属性species。然后,生成两个实例对象,这两个对象的species属性是独立的,修改其中一个,不会影响到另一个。每一个实例对象,都有自己的属性和方法的副本。这不仅无法做到数据共享,也是极大的资源浪费。这样就引入了prototype
考虑到这一点,Brendan Eich决定为构造函数设置一个prototype属性。
这个属性包含一个对象(以下简称"prototype对象"),所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。
实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用的。
species属性放在prototype对象里,是两个实例对象共享的。只要修改了prototype对象,就会同时影响到两个实例对象。由于所有的实例对象共享同一个prototype对象,那么从外界看起来,prototype对象就好像是实例对象的原型,而实例对象则好像"继承"了prototype对象一样。
这就是Javascript继承机制的设计思想。
后面还有三篇~ 时间关系没有看qwq~
有时间把后面三篇继承相关的补了
var obj = { foo: 5 };
上面的代码将一个对象赋值给变量obj。
JavaScript 引擎会先在内存里面,生成一个对象{ foo: 5 },
然后把这个对象的内存地址赋值给变量obj。
也就是说,变量obj
是一个地址(reference)。后面如果要读取obj.foo
,引擎先从obj
拿到内存地址,然后再从该地址读出原始的对象,返回它的foo
属性。
原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。举例来说,上面例子的foo
属性,实际上是以下面的形式保存的。
var obj = { foo: function () {} };
这时,引擎会将函数单独保存在内存中,然后再将函数的地址赋值给foo
属性的value
属性。
由于函数是一个单独的值,所以它可以在不同的环境(上下文)执行。
一旦var foo = obj.foo
,变量foo
就直接指向函数本身,所以foo()
就变成在全局环境执行。
(因为啊,obj.foo相当于是拿到了obj对象里foo函数的地址,函数的地址就是存储的那一整个函数,然后定义var foo也指向这个这个地址,name
var foo = obj.foo
也就等于 var foo = function () {} // 此处是foo函数的具体内容)
这里是foo是函数名,如果是var foo = obj.foo()那就是会返回函数的执行结果(也就是reurn值了)
(3)有关环境变量
JavaScript 允许在函数体内部,引用当前环境的其他变量。
var f = function () {
console.log(x);
};
上面代码中,函数体里面使用了变量x
。该变量由运行环境提供。
由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境(context)。所以,this
就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。
下
面代码中,函数体里面的this.x
就是指当前运行环境的x
。
var f = function () {
console.log(this.x);
}
var f = function () {
console.log(this.x);
}
var x = 1;
var obj = {
f: f,
x: 2,
};
// 单独执行 函数f在全局环境执行,this.x指向全局环境的x f() // 1 // obj 环境执行 在obj环境执行,this.x指向obj.x。 obj.f() // 2
ES6 中的箭头函数并不会创建其自身的执行上下文,所以箭头函数中的 this 取决于它的外部函数
嵌套函数中的this不继承(负负得正)
===是全等,不转换就比较,返回true。**
下面是规范:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· Windows编程----内核对象竟然如此简单?
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用