JavaScript高阶学习笔记
1.数据,变量和内存
什么是数据?
存储在内存中表示特定信息的东西,本质是0101;
数据的特点:可传递,可运算,一切且数据
所有内存操作的目标:数据
什么是内存?
内存条通电后产生的可存储数据的空间(临时的)
内存条的产生和死亡:内存条(电路板)->通电->产生内存空间->存储数据->处理数据->断电->内存空间和数据都消失
一块小内存存储的两个数据:地址值和内存存储的数据
内存的分类:栈->存储全局变量和局部变量
堆->存储对象
什么是变量?
可变化的量,由变量名和变量值组成;
每个变量对应一个小内存,变量名对应内存中的地址值,可以通过变量名来找到内存,变量值对象内存中保存的数据;
内存,数据,变量之间的关系?
内存是用来存储数据的空间;
变量名是内存的标识;
***************************************************************************************************************************************************************
注意:变量之间的赋值是值传递,而创建对象时发生的是地址传递;
JS引擎如何管理内存:
(1.)内存的生命周期:
分配小内存空间,得到它的使用权;
存储数据,可以反复进行操作;
释放小内存空间;
(2.)释放内存:
局部变量:函数执行完后自动释放;
对象:成为垃圾对象(没有变量指向它)->由垃圾回收器自动回收
2.对象
对象是什么?
保存多种数据的容器;有利于管理多个数据
对象的组成?
属性:属性名(字符串)和属性值组成,一般自己写的时候不用把属性名写成字符串;
方法:一种特殊的属性(特指函数)
访问对象数据的方法?
(1.)用对象.属性名来访问
(2.)用对象['属性名']或者对象[变量名]来访问(用于特殊情况,比如属性名是关键字或者想用变量时)
3.函数
函数也是对象,只不过保存的数据是代码,函数不能添加自己的属性或者方法,只能设置this的属性或者方法给它的实例对象使用;
创建函数的方法有三种:a.声明式 function xxx(){} b.赋值式 var xxx=function(){} c.调用构造函数 var xxx=new function()
*************************************************************************************************************************************
回调函数的定义:a.自己定义的 b.自己没有调用 c.函数最终执行了
常见的回调函数有:a.dom事件回调函数 b.定时器回调函数 c.ajax请求回调函数 d.生命周期回调函数
*************************************************************************************************************************************
IIFE:即匿名函数自调用,(function(){})()这种形式.最外层加上括号是为了把这个函数看成一个整体,优点是隐藏实现,不污染全局环境;
这里需要注意:JS必须加;的地方有两个,一个是小括号开头的语句前面要加分号,第二是中方括号开头的语句前面加分号;IIFE是第一种;
*************************************************************************************************************************************
关于函数中的this:
任何函数本质上都是通过某个对象来调用来(new一个实例对象也算是调用了函数,创建对象时就调用了构造函数,这时的this是那个实例对象)
所有函数内部都有一个this,它的值是调用函数的当前对象
(在这里我的理解是,只能是实例对象来调用函数,缺了明确的对象调用外,都是window对象来调用的函数,如果在全局声明一个函数,那么就相当于与给window对象添加了一个方法属性;)
4.原型链

每个函数都有prototype属性(保留的是地址值,显示原型),指向它的原型对象,默认是一个空的object空对象(但是一些特殊的,比如内部函数,会有设定好的object对象属性);
每个实例对象都有-proto-属性(地址值,隐式原型)指向他的原型对象;不能直接操作隐式原型(ES6之前),但能操作显示原型;
原型对象中有constructor指向实例对象的构造函数;
构造函数对象被创建时,自动添加prototype属性和原型的constructor属性,之后当创建函数的实例时,由prototype赋值给实例的-proto-属性;
*************************************************************************************************************************************
原型链的别名是隐式原型链,可见这个链主要是靠对象的-proto-属性来传导的;
原型链是为了查找对象的属性,
流程是:先在自身查找,找到返回|如果没有则沿着-proto-链找,找到后返回|如果还是没有,则返回undefined;
在原型链中,需要注意:所有函数都是Function的实例,包括它本身;object的原型对象不是对象类型,它的原型是空;
读取对象的属性时,会从该对象的原型链中寻找,如果是设置对象的属性值,不会查找原型链,而是直接添加此属性;
方法一般定义在原型上,属性一般通过构造函数定义在对象本身上;
*************************************************************************************************************************************
关于Instanceof方法 A instanceof B 如果B函数的显示原型在A对象的原型链上,则返回True;把A看成实例对象走-proto-,B看成函数走prototype,B走一步,A随意走,如果能重合则返回true;
5.执行上下文和执行上下文栈
执行上下文:
(1.)全局代码:在执行全局代码之前,会有一段预处理操作,首先将window确定为全局执行上下文对象,然后
var声明的变量赋值为undefined并且添加为window的属性,
函数声明形式创建的函数赋值给函数名,并且添加为window的方法;
this设置为window;
(2.)函数部分:在执行函数代码之前,创建对应的函数执行上下文对象(不是这个函数本身),然后;
var声明的变量赋值为undefined并且添加为函数执行上下文对象的属性,
函数声明形式创建的函数赋值给函数名,并且添加为执行上下文对象的方法;
this赋值为调用函数的对象;
所以想要读函数的某个数据其实是去执行上下文对象里去找,但这个对象不是实际的对象(只是栈里数据的集合);
执行上下文栈:
在全局代码执行前,JS引擎就会创建一个栈来存储管理所有的执行上下文对象,栈是后进先出的顺序,是n+1的结构,1是window这个全局上下文对象,n是函数的上下文对象;
在全局执行上下文(window)确定后,会将其添加到栈中(压栈),在函数执行上下文创建后(即函数被调用后),将其添加到栈中(压栈),在当前函数执行完后,将栈顶的对象移除(出栈),当所有代码执行完后,栈中只剩下window;
6.作用域与作用域链
作用域是静态的,不同于函数上下文这种动态创建的,作用域是在代码写好后就确定了,且不会变化
分类有全局作用域,函数作用域和块作用域(ES6之后),作用是隔离变量,不同作用域下的同名变量不会冲突;
上下文对象与作用的联系是,对应的上下文是从属于对应作用域的;
作用域链就是多个上下级的作用域形成的链,它的方向是自内向外的,
首先在本作用域中寻找变量,然后去上一级的函数寻找,这样一直找到全局;
7.闭包
闭包产生的条件:a.函数嵌套(内部函数在外部函数里定义,调用不算) b.内部函数引用了外部函数的数据(变量/函数)
闭包的理解:a.嵌套的内部函数 b.包含被引用变量(函数)的对象
闭包的作用:使函数内部的变量在函数执行完毕后,仍然存活在内存中(延长了局部变量的生命周期)|可以让内部函数去操作外部函数中暂留的数据
在函数执行完后,其内部声明的变量会被释放,但存在在闭包里的会被保留;我们不能直接操作函数内的局部对象,但可以通过闭包来操作(比如返回操作函数)
闭包的应用:将函数作为实参传递给另一个函数调用|将函数作为另一个函数的返回值|创建JS模块
JS模块是具有特定功能的js文件,将数据和方法封装在函数内部(私有的),只向外暴露一个包含n个方法的对象或者函数,使用时只需要调用这个对象就可以实现特定功能;
也可以直接匿名函数自调用,把该函数的方法添加为window的方法(跑到了全局作用域,这种方法更方便);
闭包的生命周期:产生于内部函数被定义时(通常是外部函数被调用),死亡于包含闭包的内部函数成为垃圾对象(没有引用指向它),会立即死亡而不是等到垃圾回收器回收;
8.继承(属性找原型链,变量找作用域链)
继承有两种方法:
a.修改原型对象,将现有函数的原型对象修改为其他函数的实例对象,链条就是变成|函数->另一个函数的实例对象->默认的object空对象->null,但有一个constructor的漏洞,这个漏洞是因为原来函数原型被替换后,它的constructor属性也没了,如果这时再向上找constructor属性,只会返回父构造函数,解决方法是手动修改,给父函数实例对象添加constructor属性指向子函数;
b.伪继承,在子类函数中调用父类函数,并且在调用时用call方法改变this属性;
一般是两种方法的组合继承,两种方法都能继承属性和方法,但原型链继承能提高复用性,适合用来继承方法,伪继承偏向个性化,适合用来继承属性;
9.进程和线程
(1.)内存泄露和内存溢出:内存溢出发生在运行程序需要的内存超出了剩余的内存时,就会出现内存溢出的错误;
内存泄漏是指一些内容没有及时清理,致使剩余内存变小,更容易发生内存溢出
主要有以下情形: 意外的全局变量(可能是定义局部变量时不小心定义成了全局变量),没有及时清理的定时器或者其他回调函数(用完了之后应该及时清理),闭包(闭包会延长变量寿命同时也会占内存)
(2.) 进程是相互独立的,线程是进程内的一个独立执行单元,是CPU的最小调度单元
一个进程中一般至少有一个运行的线程:主线程;
一个进程也可以同时运行多个线程,我们会说程序是多线程运行的;
一个进程内的数据可以提供给多个线程直接共享;
多个进程之间的数据是不能直接共享的;
多线程:提高cpu利用率,但创建了多线开销,线程间的切换开销(同一时间只能执行一个线程,需要来回切换),死锁和状态同步问题;
单线程:顺序编码简单易懂,但性能不高;
JavaScript是单线程的语言,但H5的WEB WOKERS可以多线程运行;
Firefox,老版IE是单进程的,chrome和新版IE是多进程的 ,所有浏览器都是多线程的;
(3.)浏览器内核
支撑浏览器运行的最核心的程序,不同浏览器的内核不同,同时内核由很多模块组成;

其中JS引擎也就是JS语言如前文提到是单线程的;
首先是文本解析,然后dom/css模块将其和内存绑定,变成对象,然后再布局和渲染(根据对象数据)来呈现页面;
(4.)事件循环模块
a.JS是单线程执行的:在执行程序时,先执行初始化代码(包含一些特殊的代码如设置定时器,绑定事件监听,发送ajax请求),然后再执行回调代码;
b.关于事件循环:事件队列和栈的执行顺序不一样,事件队列是先进先出,栈是后进先出;
回调代码在JS开始执行初始化代码时,发送给浏览器的管理模块管理,然后如果被触发的话,浏览器会把事件发进回调事列,等到初始化代码被执行完后,再循环遍历回调事列,一个一个执行;
造成延迟问题是因为开始执行初始化代码到执行完初始化代码有一段时间;
这个事件队列容器起到了缓存的作用,防止JS在指定时间还在执行其他程序造成错误的情况;

浙公网安备 33010602011771号