js总体笔记
作用域与全局对象
- 当在页面中引入多个js文件的时候,需要把单个js文件中的代码写入一个立即执行函数中,然后将需要使用到的东西返回出来即可,这样可以避免全局变量污染
原型与原型链
-
prototype
函数,本质上是一个特殊的对象,他的特殊之处之一就是,函数可以直接访问该函数的prototype属性,即原型,原型是一个普通对象,函数即有原型,又有隐式原型,原型prototype默认存储的为constructor即构造函数本身的函数体 -
proto
此为隐式原型,所有通过构造函数new出来的对象都具有此属性,此属性也是一个对象,这个对象中存储了构造函数的prototype -
原型链
所谓原型链,即在对象诞生过程中的,构造函数具有自己的prototype,通过new创造出的实例具有__proto__属性,该属性存储了构造函数的prototype,这样就形成了一条链,当在构造函数的原型上添加一些数据或者方法时,通过该构造函数创建的对象也可以通过自己的__proto__(隐式原型)找到构造函数的原型从而使用构造函数原型上的方法和数据
当访问实例成员时(对象的某个值)现在自身上找,如果自身找不到就会通过__proto__找到构造函数的原型,然后在构造函数的prototype上找,由于构造函数也是对象他也具有隐式原型,当在构造函数的原型上也找不到时就会继续向上查找,这个时候一般就找到Object(或者Array..),这就是为什么可以声明一个对象,他身上没有toString方法却可以直接使用的原因笔记
预编译(变量声明提升)
- 函数声明整体提升(在流程赋值前可调用,因为整体提升到最前),变量声明仅声明提升(在流程赋值前输出undefined)
- 若变量未经声明就赋值则此变量归全局window所有,一切声明在全局的变量都归window所有
- 复习
- 2022/7/6
- 执行器上下文理解
js在运行执行的时候js引擎会创建一个栈,首先这个栈会创建一个全局的环境放进栈里,即全局GO对象
这里会把全局的函数和变量声明提升上来,当遇到函数执行的时候会创建该函数的执行期上下文并将之
放入栈顶,执行完毕后移除。全局的GO对象应该是不会移除的,否则异步函数执行的时候就没有全局对象
了。
- 执行器上下文理解
- 2022/7/6
- 复习
预编译过程(首先会生成全局的GO对象)
- 创建AO对象(执行期上下文)
- 找形参和变量声明将变量和形参作为AO的属性名,值为undefined
- 将实参和形参相统一
- 在函数体中找函数声明,将函数名作为AO对象的属性名挂起来然后将相应的函数体赋值到对应的属性名下
- 函数正式执行,将读到的赋值语句赋值给AO对象中的相应属性
作用域和作用域链
- 原理
作用域链是基于执行期上下文而存在,在js引擎创建的栈中,栈底始终为全局的的执行期上下文,可以将之看作一个对象
为了该上下文中代码的运行,该对象中有一个VO对象,这个VO对象存储的实际就是全局的在预编译期间提升上来的东西即
全局的this,代码在执行的时候每个函数执行都会创建自己的VO对象,函数执行创建的VO对象会将其所属环境的VO对象存储
进去,函数运行时查找变量的时候就会首先在自己的VO上查找,若找不到就会通过scope中存储的当前所属VO查找父环境的
VO对象,这样的嵌套即为作用域链
闭包
- 闭包原理
- 简单的来说就是内部执行期上下文的函数被保存到了外部则必定生成闭包
- 实例讲解
function test() { function a() { var bbb = 10; console.log(aaa); } var aaa = 100; return a; } var bag = test(); bag();
在执行完test函数时本应销毁test的AO,然而在执行最后一行的时候将function a 返回到了外部作用域,在执行被返回出去的a函数时又要用到
test的AO,所以函数a身上依然保有函数test的AO,这就形成了闭包,实际上就是test的执行期上下文被bag拿着了
在预编译的时候读到函数就会开始构建基础执行期上下文了,就像在全局读到test虽然还没有执行test但是test的AO中的第0项已经指向GO了,在
执行的时候test创建自己的执行上下文才将第0项的GO移到第一项,将自己的AO放在第0位 - 闭包的危害以及应用
- 危害
- 闭包会造成原有的作用域链不释放,造成内存泄露,即内存被占用的越来越多可用的就越来越少
- 应用
- 实现公有变量
累加器 - 可以用来做缓存
方法中定义一个数组或者对象用来存储数据,将添加和删除的方法返回出来这样如果调用方法添加了数据由于作用域链不释放就将数据
缓存了下来 - 实现私有化
- 模块化开发防止污染全局变量
- 实现公有变量
- 危害
立即执行函数
- 有的函数只被调用一次但是如果正常写那么在全局的GO中它的空间却会被一直占用,要解决这个问题就用立即执行函数,立即执行函数执行完毕后
立即销毁自己,释放自己占用的内存 - 原理
- 函数声明后边直接加括号是不会执行函数的还会报错,因为只有表达式才能被执行符号执行
- 被执行符号执行的函数表达式会立即放弃自己的名称
以上函数是可以执行的,但是再拿a去访问函数就访问不到了,这就是立即执行函数的原理,加一对括号将函数变成表达式然后用执行符号执行var a = function (){ console.log("hello world") }()
对象
- 对象内部的方法中的this指向对象本体
- delete one.a 用来删除对象中的某一项
包装类
- 原始值
原始值是不能有属性的,但是对象原始值是可以有属性的,让一个原始值成为对象原始值 new Number(123) 它可以参与运算,但是运算后它
就变回原始值了 - 原始值调用属性不报错的原理(包装类)
原始值理论上是绝对不允许有属性和方法的,但是在创建一个数字变量,然后给这个变量附属性,却并没有报错,然而在访问附上去的这个属性
的时候却又显示undefind,其原因是因为中间经过了隐式的包装类的过程
在给原始类型变量附属性的时候底层会将数字本体用Number()进行一次包装让其变成原始值对象,这样原始值对象就可以加属性不报错了,执行完毕
之后被包装出来的这个原始值对象就没用了垃圾回收机制会直接将它回收,原先的原始值变量还是原始值变量,在访问这原始值变量之前被附的属性
的时候又会进行一次包装,它会用Number将这个数字创建一个原始值对象,然后去访问这个新创建的原始值对象,其值就为undefind了let a = 1; // 创建原始值 a.name = 111; // 原始值变量不进行任何操作,这里隐式 Number(1).name = 111 然后删除,就不会报错 console.log(a.name) // 上一步创建的原始值对象已经删除,这里访问再次包装,访问的其实是 Number(1).name,返回undefined - 包装类总结
包装类就是在给原始值进行属性操作的时候为了不报错而进行的中间过程,自负床之所以可以访问length是因为在访问的时候创建的包装类 string()
本身就有length属性
构造函数
- 构造函数的原理
构造函数在创建的时候会在最开头创建一个this,this的一开始就会将__proto__指向这个构造函数的原型,后边加入的属性都是加入到这个
this里,在代码结束的时候的最后边会隐式的 return this如果手动返回一个空对象则前边写的失效,但是如果返回的是原始值则会忽略这个返回
原型与原型链
- 原型
- 构造函数本体属性
- prototype
此为构造函数身上的属性,即为原型是所有通过该构造函数创建的对象的公共祖先,被创建对象在查询自身属性的时候会先在自身查找,如果
没找到就会到创建它的构造函数的原型上查找- constructor
构造器,为prototype对象的属性之一,它指向构造函数的函数体,可以通过运行符号运行构造函数
- constructor
- prototype
- 被创建对象属性
- proto
隐式原型,所有的对象都有此属性,此属性指向创建本对象的构造函数的原型- constructor
构造器,为隐式原型的属性,指向构造函数原型的constructor属性,构造函数原型的构造器如果更改则本属性同样更改,
被构造对象的构造器如果更改也会让构造函数的构造器发生更改,进而让所有通过此构造函数创建的对象的构造器发生更改- 修改constructor发生链式修改的原因
在构造函数被new的时候会在函数体第一行隐式的创建一个this这个this不是空的,它将自己的__proto__赋值为
构造函数本体的prototype这里就将引用存入了,后续对象在自身访问不到属性的时候就是通过这个__proto__
存的原型引用来在原型上查找的,因为这是一个引用所以它们操作的其实是一个对象即原型本身,任何一方把其中的
属性修改,其他的对象等后续访问的空间没有变则都受影响
- 修改constructor发生链式修改的原因
- constructor
- proto
- 原型总结
构造函数的prototype和被创建对象的__proto__之间的关系是__proto__里存着的是prototype的引用,任何一方发生修改则会影响构造函数的
prototype和所有被创建对象,被创建对象在查找自身属性无果的时候就会通过__proto__中存的索引去构造函数的原型上查找
- 构造函数本体属性
- 原型链
构造函数创建对象的过程是需要new,然后在执行构造函数的最开始隐式创建一个this对象,将this对象的__proto__属性赋值为构造函数的原型
当对象访问属性自身没有的时候就会通过__proto__索引找到原型,原型上如果没有的话,就会找构造函数的__proto__,然后找到创建构造函数的
构造函数的原型,这就构成了链式结构,这就是原型链
原型链的连接点是__proto__
父属性构造函数身上的引用属性可以修改,原始属性无法修改
call/apply
- call
调用call方法的东西的this指向将会被改向call的参数1,后续参数会当作实参传入调用者 - apply
功能和call相同,只是传入参数不同,apply要给调用者传参需要用数组apply(this,[]) - bind
此方法为函数原型链上的方法即每个函数都可以调用此方法,此方法返回一个函数,此函数的this执行bind函数的参数,其功能为改变函数的this指向
继承
- 传统形式(原型链继承)
- 继承过多无用属性
- 借用构造函数
- 利用call和ap ply实现
- 缺点
- 不能继承借用构造函数的原型
- 每构造一次多调用一次函数
- 共享原型
- 多个构造函数共用一个原型
- 缺点
- 无法实现每个构造函数的私有原型属性,会互相影响
- 圣杯模式
- 还是使用共有原型但是中间加一个隔层
将需要共享的原型放在一个公共祖先构造函数上,然后创建一个中间构造函数它的作用是继承公共原型,然后将需要公共原型的构造函数的原型function Fat() { } Fat.prototype.name = "li"; function Mid() { } function Son() { } Mid.prototype = Fat.prototype; Son.prototype = new Mid(); Son.prototype.age = 18;
赋值为中间隔层new的对象,就实现了各构造函数既有公共属性,又可以有私有原型
- 还是使用共有原型但是中间加一个隔层
深度克隆(对象的方法)
- 确定参数类型
参数的类型有function/obj/NAN/null,但是由于一些历史遗留原因导致一些类型难以确认- 确定参数类型的方式及原理
- toString.call(target)
调用toString,让同String方法的this指向需要检测的属性,就会返回一个字符串形式的数组,取其第二位就是参数的准确类型- 原理
Object的原型上有toString方法,其他的每个引用类型都有自己的重写的toString方法,那么可以推测所有的调用toString
都会首先经过object的toString方法进行this的类型判断,然后通过这个this去调用各类型的重写方法,那么我只需要把
object的判断截胡出来就可以实现判断了,把一个让调用toString的this变成一个未知数,就用call方法改变其this指向即可
- 原理
- hasOwnProperty
所有的对象都有此方法,此方法用来判断传入参数是否是自己的属性的,如果是原型上的属性就会返回false - in
功能和hasOwnProperty相似,但是此方法只要在自身或原型链上能查到就会返回true(只要可以访问到hasOwnPro) - instanceOf
看A的原型链上有没有B的原型,用来区分具体的属性数据类型
- toString.call(target)
- 确定参数类型的方式及原理
- 克隆一个函数
需要使用Function来构造函数let a = function() { console.log("hello world") } let b = new Function("return " + a.toString())(); // 这一句的意义是创建一个如下函数 // function(){ // return function() { // console.log("hello world") // } // } // 然后立即执行,Function会将传入的字符串当代码执行 - 深度克隆原理
- 首先需要一个判断属性的具体种类的方法,该方法返回一个字符串
- 然后需要一个通过第一个方法返回的判断结果对传入属性进行复制的方法
- 主方法,遍历传入的对象,判断其是不是object如果不是则调用复制方法进行复制,如果是则调用函数自己并将此对象传入进行递归操作
数组
- shift 从数组前移除
- unshift 从数组前添加,可添加参数
- sort 按传入规则排序数组
- reverse 你转数组
- split 以参数分割调用者为数组并返回(字符串方法)
- slice 数组切片并在切口处添加数据
- pop 将数组最后一位剪切
- join 以参数为连接点连接数组内容
for/in和for/of的区别
- in可以遍历对象的key,of只能遍历可迭代对象
- in会将原型链上的key遍历出来,of不会
类数组
- 什么是类数组
一个对象的属性是索引属性,具有length属性,最好有push方法的对象叫类数组 - 类数组的push方法主要看length,按照length进行赋值
- 好处
可以将数组和对象的有点拼接,既有位查询又有自己的属性,可以for/in遍历 - 数组去重
利用对象的属性名不会重复的特性
try/catch
es5严格模式
- 禁止
- with
将传入的对象放在当前执行期上下文创建者的scope的最顶端【改变作用域链】 with(){} - arguments.callee
- function.caller
- 禁止重复属性或参数
- 禁止使用eval() 因为它可以改变作用域
- with
- 要求
- 变量赋值前必须声明
- 局部的this必须被赋值(用call)
dom
- 事件绑定
- addEventListener(事件类型,函数,)
一个事件添加多个处理函数 - on
- obj.attachEvent("on" + type, function)
ie独有- 事件模型(一个对象的一个事件类型只能遵循一种事件模型)
- 事件冒泡
从子到父 - 事件捕获
addEventListener第三个参数改true就变成事件捕获模型
把结构的最外层先抓住然后一层层向内走到触发事件的层 - 阻止冒泡
- e.stopPropgation
- e.cancelBubble = true ie独有
- 事件冒泡
- 事件模型(一个对象的一个事件类型只能遵循一种事件模型)
- addEventListener(事件类型,函数,)
- 阻止默认事件
- return false
- e.preventDefault
- event.returnValue = false
json
- 什么是json
一个属性名为字符串的对象就是json格式,前后端传递的是二进制的文件,所以这个对象在传输的时候需要被封装为字符串 - json相关方法
- JSON.stringify(obj)
返回传入json对象的字符串 - JSON.parse(obj)
字符串json转对象
- JSON.stringify(obj)
正则表达式
- 正则的声明
let a = /abc/ - 使用
a.test(str) 返回布尔值 - 关键字(加在正则末尾)
- i
忽略大小写 - g
全局匹配,找到一个继续向后找 - m
多行匹配
- i
- 表达式
一个[],表示一位表达式,内部书写规则如匹配0-9的数字 [0-9]
作用域链和原型链的理解
- 作用域链
作用域链的存在是基于js执行引擎所创造的js执行栈而存在的,作用域即每个函数的执行环境,所谓执行环境即需要的各种变量
作用域在初始化的时候会将该函数环境下的所有的变量挂载到该环境的变量对象上以供函数执行使用,嵌套而形成作用域链,其本质是为了函数变量的查找 - 原型链
所谓原型即为一个构造函数的公有属性,一个函数都有自己的prototype,每一个对象都已__proto__,proto指向创建自己的函数的prototype
js总体笔记
http://localhost:8090/archives/jszong-ti-bi-ji