1、总结特点:
首先this就是指代的运行环境,其次容易搞混的xx的函数都转换成函数地址和代码块就好懂多了
在看了这么多之后,自己的总结:
this作用域这块大概是谁调用这个函数,函数里的this就指向谁,如果是两套a.b.c() 那么指向是离函数c()最近的b里面,如果是var f=b.c 这样就只是简单生搬了c里面的内容和b没什么关系。
bind就是F是Function F.call(obj,参数)
实际上就是obj.F,
obj内部写了一F的方法,让F可以访问obj内部的东东
A.call(B)就是B.A 这样a可以访问b里的东西,a里的this也指向了B
(可以理解成var a={a里面原来的东西+函数B},a.B就和最正常的例子一样了-.-)
这样是最标准的,安乐的this环境,一定是对象里的函数~
var obj={
var a=1;
function:{ 。。。this.a 。}
}
这里更详细了一下apply的妙用性原理
上面2这篇文章ok,但是这人其他的比如他引用的那个call文章不行(?)
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]
(1)[].push只是相当于调用了数组的方法,就像是Array.prototype.push一样(重点还是在push)
(2)上面的代码就是list1.push(list2的具体参数)
list1.push(参数就是list2因为apply整的解构
(3)不过es6下『...』的出现也能做到同样的事情了
ES6 写法: list1.push(...list2)(等同于上面的代码)
(4)此外 let max = Math.max.apply(null, array);如传入的是null或者undefined那么自动会变成window.xxx
3、new的时候发生了什么?
直接看MDN说的:
new
关键字会进行如下的操作:
- 创建一个空的简单JavaScript对象(即
);{}
- 为步骤1新创建的对象添加属性__proto__,将该属性链接至构造函数的原型对象 ;( ....__proto==constructor.prototype(那么杰哥原型链那些,就是链接到了属性共享的方面~)
- 将步骤1新创建的对象作为
的上下文 ;(实际上就是call改变那句话,构造函数.call(实例)这样就相当于实例.构造函数,使得函数里的this指向实例====》实际上【就是对象作为了this上下文】this
- 如果该函数没有返回对象,则返回
。this
4、【继续深究(思考之后得到的)】
1 call apply调用的是什么?是马上调用吗?
可以明确从apply应用多参数的push那里感知到,call这么一行话写了就是调用的,相比于bind是要加个括号再调用的,也可以感受到这一点。
也就是A.call(B)就相当于B.A() 一定是A()因为调用了,马上执行~
或者是A.call(B,C,D)就相当于B.A(C,D) cd是伪装在那里的A调用的参数
此时AB是正常的夫妻关系,函数A可以访问B里面的方法,因为函数A的this环境已经被改成对象B了
2 call做完返回值是怎么样的呢?
应该就是函数的返回值了,很多情况下没有返回值所以没有接着,但是比如slice() 这种返回的新数组就要设立返回值啦
3 对象嵌套与函数嵌套与this怎么理解,为什么函数嵌套不继承this,为什么对象嵌套也不继承this?
只要记住:谁最后调用,函数的this就归谁,
没人调用this就归window了
① 对象嵌套,最后是b先调用的,那么this就归b啦
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this.a); //12
}
}
}
o.b.fn();
② 函数嵌套。
var obj = {
a1:1,
foo: function () {
console.log(this.a1,"到此处还是正常访问的");
var fn = function () {
console.log(this);//这里就已经完全不能继承,并且整到全局去了
}
fn()
}
obj.foo()
表面上,这能说明嵌套函数不继承this
但实际上怎样导致的呢?
因为obj调用了foo,所以foo可以继承obj里面的,但是又没有人调用fn()。
没人调用fn,那就只能window了,window下面又没有a1,那么打印this也是打印的window下面的this
头脑风暴:如果使得fn被调用呢,怎么修改代码?
①emmm。return fn()的话本质也是先调用fn() 再看fn的返回值是啥返回那个值,还是没有做到xxx.fn
② 如果是在函数内先扒拉扒拉。。。
好吧,函数要写语句的,不能写成员变量,所以这个问题先无解,
但确实是可以理解为嵌套函数不继承的。~~
4 Object.prototype.toString.call(arr )这里call是干嘛的呢
比如 var arr =new Array()
仔细一想,这不就是A.call(B)嘛 ,通俗一点就是B.A() 那也是就arr.toString? 为什么不直接这么用呢
嗯~~~ 因为大多数对象,
toString()
方法都是重写了的,没有重写的那些当然就返回一样的结果啦,为了统一就用食物链顶端的Object来做法了。
因为,想想啊比如number.toString就是把number转换成字符串,var array=[1,2,3]这里就是转换成"1,2,3",所以说其实都荡~然~无~存~ 所以还是乖乖的用object. xxxxx吧
5 括号问题
注意 以[].push.apply(list1,list2);为例 你想用的是push() 但实际上你在调用的时候是没有括号的 有括号就变成函数调用(立刻调用那种) 而需要传参数的一定在call的最后 那么前面就不要有括号了)
6 let max = Math.max.apply(null, array);
===》max= window.max(array)
5、事件循环机制
随手整一下事件循环,趁着现在脑子清醒(new和原型链还没有整理)
具体道理我都懂,只是我想知道的是,是谁把那些异步的东东在什么时间加进去的?
这个看了MDN和阮一峰也没有详细说明,倒不是说像new那样错了的问题了就是他们都仔细没说哈哈哈
· 主线程:也就是 js 引擎执行的线程,这个线程只有一个,页面渲染、函数处理都在这个主线程上执行。
· 工作线程:也称幕后线程,这个线程可能存在于浏览器或js引擎内,与主线程是分开的,处理文件读取、网络请求等异步事件。
主线程在执行栈里执行同步代码,而异步的?
实际上这些异步的操作,是有一个“幕后线程”来执行的,也是它, 在
『ajax得到回复、 settimeout 时间轮到 』的时候加入任务队列。
任务队列优先极是因为有宏任务和微任务两个管道,不是因为自动排序。
宏任务就已经是task queue了,或者Macrotask ,微任务是microtask。
但实际上真的是因为先去宏任务再微任务吗?不是的……
还是 看那篇文章: 简而言之,一次事件循环只执行处于 Macrotask 队首的任务,执行完成后,立即执行 Microtask 队列中的所有任务。(如果微任务在当前队列有新增的,也是继续执行微任务的)
原因:因为一开始js主线程中跑的任务就是macrotask任务,而根据事件循环的流程,一次事件循环只会执行一个macrotask任务,因此,执行完主线程的代码后,它就去从microtask队列里取队首任务来执行。
这个,一般人可能都没太注意到,因为表现出来是优先极,但实际去看的话,嗯……
这里也说了,确实是一个宏任务--> 所有微任务 --》UI渲染. 这篇就讲的很深了,而且涉及到了vue的nextTick机制
其中script(整体代码)是算作一个宏任务的
注意:
由于在执行microtask任务的时候,只有当microtask队列为空的时候,它才会进入下一个事件循环,因此,如果它源源不断地产生新的microtask任务,就会导致主线程一直在执行microtask任务,而没有办法执行macrotask任务,这样我们就无法进行UI渲染/IO操作/ajax请求了,因此,我们应该避免这种情况发生。在nodejs里的process.nexttick里,就可以设置最大的调用次数,以此来防止阻塞主线程。
虽然 V8 是单线程的,但底层的 C++ API 却不是。这意味着当我们执行一些非阻塞的操作,Node会调用一些代码,与引擎里的js代码同时执行。一旦这个隐藏的线程收到了等待的返回值或者抛出一个异常,之前提供的回调函数就会执行。
在主线程执行栈空闲的情况下,从任务队列中读取任务入执行栈执行,这个过程是循环不断进行的,所以又称Event loop(事件循环)
(顺带一提,有关浏览器。。)
浏览器内核即浏览器底层最核心和最基础的那一部分,它主要负责对网页当中的html、css、JavaScript进行解释然后在浏览器当中进行渲染最终呈现给用户,也就是说内核的工作就是渲染
整个浏览器是多进程的,平时我们经常接触的是JS引擎和渲染引擎都是属于单个进程(单个tab页)的一个线程。
那么js引擎是单线程的~ 渲染引擎~还有异步引擎和定时器引擎
但是处理还是js引擎在做,只是说等待事件返回是别的线程去处理的。所以事件循环和引擎这几个关系不大~
6、原型链系列。。。 因为new的时候也有原型链就去复习了一下,不过明天再写吧
怎么说呢,大概就是其实网上的东西鱼龙混杂,你找得到的不一定是你想看的
官话:(?)
__proto__
通常称为隐式原型,
prototype
通常称为显式原型,那我们可以说一个对象的隐式原型指向了该对象的构造函数的显式原型。那么我们在显式原型上定义的属性方法,通过隐式原型传递给了构造函数的实例。这样一来实例就能很容易的访问到构造函数原型上的方法和属性了。
自己的理解:
原型链首先知道函数才有prototype,其次那个prototype也是一个实例对象,只不过①它是js底层给我们创建好的所以可以共享②新加的需要共享的也可以填上去③归根结底也只是一个可怜的实例罢了
关键点:实际上xxx.prototype也是一个可怜的实例罢了,只是js帮我们创建好了,实例就是比如var a=new Array() a就是实例 那么a.__proto__就是object通过var array=new Object(xxxx) array这个类实际上也是object创建的实例 只不过是现成的……
添加各种类型创建出来的(好像讲的有点乱,可以去后面看图)
具体:
(1)首先要知道概念:
①
__proto__
、
constructor
属性是对象所独有的;
② prototype
属性是函数独有的;
③ js中函数也是对象的一种,那么函数同样也有属性
__proto__
constructor
;
其实也就是对象有__proto__和constructor,函数有__proto__和constructor和prototype,作为一个对象是没有prototype的(这个是构造函数才~ 有~ 的~ )如果一个新的实例没有constructor,那么就会去原型链__proto__去找,这也是新实例.constructor==array()的原因(但是去.hasOwnProerty是false~ 自己没有,只是去原型链找了而已~ )
(2)函数、构造函数的区别:任何函数都可以作为构造函数,但是并不能将任意函数叫做构造函数,只有当一个函数通过new关键字调用的时候才可以成为构造函数。
var Parent = function(){
}
//定义一个函数,那它只是一个普通的函数,下面我们让这个函数变得不普通
var p1 = new Parent();
//这时这个Parent就不是普通的函数了,它现在是一个构造函数。因为通过new关键字调用了它
//创建了一个Parent构造函数的实例 p1
下面分别介绍这三个
(1)prototype 结合下文的prototype其实就=可共享的,那么函数指向的prototype就是孩子们可以共享的“爸爸” 的各种。。。
为了方便举例,我们在这模拟一个场景,父类比作师父,子类比作徒弟。师父收徒弟,
徒弟还可以收徒弟。徒弟可以得到师父传授的武功,然后徒弟再传给自己的徒弟。
师父想要传授给徒弟们的武功就放到“prototype”这个琅琊福地中。徒弟徒孙们就去这里学习武功。
prototype属性可以看成是一块特殊的存储空间,存储了供“徒弟”、“徒孙”们使用的方法和属性。
(只有函数才有的prototype)
(下面是null)
prototype是函数独有的属性,从图中可以看到它从一个函数指向另一个对象,代表这个对象是这个函数的原型对象,这个对象也是当前函数所创建的实例的原型对象。
prototype
设计之初就是为了实现继承,让由特定函数创建的所有实例共享属性和方法,也可以说是让某一个构造函数实例化的所有对象可以找到公共的方法和属性。有了
prototype
我们不需要为每一个实例创建重复的属性方法,而是将属性方法创建在构造函数的原型对象上(prototype)。那些不需要共享的才创建在构造函数中。
继续引用上面的代码,当我们想为通过Parent实例化的所有实例添加一个共享的属性时,
Parent.prototype.name = "我是原型属性,所有实例都可以读取到我";
这就是原型属性,当然你也可以添加原型方法。那问题来了,
p1
怎么知道他的原型对象上有这个方法呢,往下看↓↓↓
(2)proto属性
__proto__属性相当于通往prototype(“琅琊福地”)唯一的路(指针)
让“徒弟”、“徒孙” 们找到自己“师父”、“师父的师父” 提供给自己的方法和属性
(这里其实把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
就是根构造函数。
自己手画了张图:
后续思考
礼貌发言:
实际上那个课除了内容不深入,就广度上来说还是可以的。谁能想到instanceof手写这种真的会考到呢,155551
思考:
看到手写instanceof的时候
形式是 arr instanceof Array
手写是左边.__proto__===右边.prototype
(1)为什么是array不是()或者[]
打印Array,可以看到控制台返回的就是 ƒ Array() { [native code] },也就是这里右边其实是指代的函数那边的
所以要左边的隐式原型是否等于右边的显式原型
既然Array就指代了Array(),那arr.__proto__呢?其实只是Array.prototype,又回到了起点,函数其实是很高的,这个prototype才是弟弟
var obj={} obj.__proto__ 你感觉是应该到Object了吧,实际上打印的并不是object而是Object.prototype,也就是函数Object()的狗(Object创建出的函数 给下一代共享的方法,Object创建出的是obj不是这个Object.prototype)
↓ Object.prototype:
{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
表面上,我去试试Object.__proto__是不是null?但实际上你要知道这个时候你找到的是Object函数的__proto__,这个时候Object函数是作为一个实例的,它的__proto__是Function.prototye 然后就还是那一堆代码
实例的.__proto__也就是构造函数的prototype是一堆共享代码,这个是不能比较的!!!!!所以也只能去比较构造函数!!!!!!
那么我们大胆一点,Object.prototype.__proto__==null虽然这个是终点了,但是Funtcion那里才是不停指向自己的死循环,那么不妨可以假设这个Function才是背后boss终极老鸨子…… 实际上后面那些不都是它先搞了构造函数,构造函数整的实例和共享对象们(实例的隐式.__proto__ 函数的显示prototype)
(2)为什么是===
因为需要确定是相同而不是相等
这么说如果用的是== 并且你的链子上有重名的,那就会被判断相等~ 但实际上并不是两个相同的东东啊
自己尝试一下,那就先从写构造函数开始吧(构造函数的名字在这里担任了类名这一角色)
function qwq(){
this.qwqq=1;
var obj1=new qwq()
哇呀,卡住了…… 我只会从object生成构造函数,不会继承qwq函数呀…… 果然继承没有好好学.jpg
补了继承再来新增
之前不知道纯粹是因为没自己下手试试吧。。。就会发现实际上有这么多问题
所以说不是我开始追根究底了
只是之前的我太不会动手实践了 人家课什么的没啥问题
最后,这里是偏向原理的部分,为什么这么设计(?)
https://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html
(1)首先,js里一切都是对象,
如果真的是一种简易的脚本语言(设计初衷),其实不需要有"继承"机制。但是,Javascript里面都是对象,必须有一种机制,将所有对象联系起来。所以,Brendan Eich最后还是设计了"继承"。
但是,他不打算引入"类"(class)的概念,因为一旦有了"类",Javascript就是一种完整的面向对象编程语言了,这好像有点太正式了,而且增加了初学者的入门难度。
(2)他把new命令引入了js,用来从原型对象生成一个实例对象。但是,Javascript没有"类",怎么来表示原型对象呢?
这时,他想到C++和Java使用new命令时,都会调用"类"的构造函数(constructor)。他就做了一个简化的设计,在Javascript语言中,new命令后面跟的不是类,而是构造函数。
『仔细想想,确实var c=new Array() 首先是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~
有时间把后面三篇继承相关的补了
这本书不错,能找到什么在线版本吗?https://www.bookstack.cn/read/javascript-tutorial/README.md
最后附上阮一峰this这篇的的教程
1、理解诸如var foo = obj.foo的形式原理
从函数是存储的相关地址开始(函数名实际上只是保存函数地址的一个变量,也因为这样函数可以这样在不同上下文(环境)执行,加上JavaScript 允许在函数体内部,引用当前环境的其他变量。this就指代了当前的运行环境。。)
(1)对于
var obj = { foo: 5 };
上面的代码将一个对象赋值给变量obj。
JavaScript 引擎会先在内存里面,生成一个对象{ foo: 5 },
然后把这个对象的内存地址赋值给变量obj。
也就是说,变量
obj
是一个地址(reference)。后面如果要读取
obj.foo
,引擎先从
obj
拿到内存地址,然后再从该地址读出原始的对象,返回它的
foo
属性。
原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。举例来说,上面例子的
foo
属性,实际上是以下面的形式保存的。
↑ obj=foo对象的地址
↓ obj=foo对象的地址 foo对象=函数的地址
(2)对于函数
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=当前的运行环境
下
面代码中,函数体里面的
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
啊啊先总结特点:
上面这些,只是懂了函数的指代,具体this在干什么还是不懂,
(4)在函数里!全局调用函数里面的this还是指向全局,只有调用了对象里面的this才可以保存下来。
ES6 中的箭头函数并不会创建其自身的执行上下文,所以箭头函数中的 this 取决于它的外部函数
嵌套函数中的this不继承(负负得正)
不得不说,游戏确实挺能消磨人的耐性。。今天我和昨天玩的内容就差不多,甚至做得事情还多一些,然鹅一个9小时一个4小。。。
本来上面这些,在公交车上回来路上想的很清楚了,结果回来清了个体力的功夫,当时的豪情壮志荡然无存了_(:з」∠)_
脑子能够灵光一现的时候可太少了呀