一 .数据类型的核心操作原理
1.js中的数据类型
①.基本数据类型(值类型)5个
Number String Boolean Null Undefined
直接按值操作 例如:var a = 12 直接把12这个值赋值给a(让a变量和12这个值建立连接关系)
②.引用数据类型
按引用地址操作
1)对象 普通对象 数组 正则 Math对象
在js当中遇到对象会严格按照如下的步骤操作:
①:浏览器为其开辟一个新的内存空间,为了方便后期可以找到这个空间,浏览器给空间分配一个16进制的地址
②:按照一定顺序,分别把对象的键值对存储到内存空间中
③:把开辟内存的地址赋值给变量(或者事件)以后变量就可以通过地址找到内存空间,然后进行一些操作
备注:变量定义对象,内存地址可变,常量定义对象,定义地址不能变 但是空间内容是可变的,就是值是可以改的
2)函数 普通函数 类
①:创建函数
1.先开辟一个新的内存空间(为其分配了一个16进制的地址) 类比对象开辟空间
2.把函数体中编写的js代码当做‘字符串’存储到空间中(函数只创建不执行没有意义 因为里面都是字符串)
3.把分配的地址赋值给声明的函数名(function fn和var fn操作原理其实相同,都是在当前作用域中声明了一个名字
,只是一个函数名一个变量名,此处两个名字是重复的。)
②:执行函数
目的:执行函数体中的代码
1.函数执行的时候,浏览器会形成一个新的私有作用域(只能执行函数体中的代码),供函数体中的代码执行
2.执行代码之前想,先把创建函数存储的那些字符串变为真正的JS表达式,按照从上到下的顺序在私有作用域中执行
!!!备注:一个函数可以被执行N次,每次执行函数时候 ,都会生成一个新的私有作用域,互不干扰
形成的私有作用域把函数体中的私有变量都包裹起来了(保护起来了),在私有作用域中操作私有变量和外界没有关系,
外界也无法直接的操作私有变量,我们把函数的执行形成的这种保护机制叫做‘闭包’
二:JS中的堆内存&栈内存
栈内存:俗称叫做作用域(全局作用域/私有作用域),作用:①:为js代码提供执行环境(执行js代码的地方)
②:基本数据类型是直接存放在栈内存中的
堆内存:作用:存储引用数据类型值的(相当于一个存储的仓库);对象存储的是键值对,函数存储的是代码字符串
在项目中,我们的内存越少性能越好,我们需要把一些没用的内存处理掉
[堆内存释放]:var o={};当前对象对应的堆内存被变量o占用着呢,堆内存是无法销毁的 o=null;
null空对象指针(不指向任何的堆内存), 此时上一次的堆内存就没有被占用了
谷歌浏览器会在空闲时间把没有被占用的堆内存自动释放(销毁/回收) IE是引用计数(引用里面嵌套引用计数就乱了)
[栈内存释放]:一般情况下,函数执行就会形成栈内存,函数执行完,浏览器会把形成的栈内存自动释放;
有时候执行完成,栈内存不能被释放
全局作用域也是栈内存,在加载页面时候执行,在关掉页面时候销毁
三:变量提升(预解释)
含义:在当前作用域中,js代码自上而下执行之前,浏览器首先会把所有带var/function关键字的进行提前声明或定义
声明(declare):var num;在当前作用域中吼一嗓子我有num这个名了
定义(defined):num=12;把声明的名字赋一个值
理解:在当前作用域中,页面一加载就是window作用域;在私有作用域中才声明私有变量。
带var关键字的只是提前的声明一下;带function关键字的在变量提升阶段把声明和定义都完成了。
console.log(num) //undefined(只声明没定义) console.log(fn) //函数本身(声明定义了) fn() //可以调用,变量提升 var num = 12 function fn(){ console.log(a) //遇到创建fn的代码直接跳过,在提升阶段声明和定义已经做过了 var a = 10; console.log(a) } fn() //fn执行会形成一个私有作用域,这时候会形成一个变量提升 console.log(num) //12
四:作用域链
1.定义变量时候带var和不带var的区别?
[带var]:在当前作用域中声明了一个变量,如果当前是全局作用域,也相当于给全局作用域设置了一个属性a
[不带var]:在全局作用域中,如果不带var,仅仅是给全局对象设置了一个新的属性名(把window.省略了),此处就不是变量
//变量提升:var a;《=》 window.a = undefined
var a =12
console.log(window.a) // 12=》window['a'] 在全局作用域中,我们声明一个变量,相当于给全局对象window增加了一个属性名
a=12 在全局作用域下,输出a会报错,输出window.a是undefined,相当于给window.a = 12 称之为window的属性 严格意义上不是变量 在js中window.开头的可以省略
2.作用域链 !!!
函数执行形成一个私有的作用域(它可以保护私有变量),进入到私有作用域中,首先变量提升(声明过的变量是私有的),
接下来代码执行,
①:执行的时候遇到一个变量,如果这个变量是私有的,那么按照私有处理即可
②:如果当前这个变量不是私有的,我们需要向它的上级作用域进行查找,上级如果也没有,则继续向上查找
一直到window全局作用域为止,我们把这种查找机制叫做 ‘作用域链’
1)如果上级作用域有,我们当前操作的都是上级作用域中的变量(假如我们在当前作用域把值改了,相当于把上级作用域中的这个值给修改了)
2)如果上级作用域没有这个变量(找到window也没有)
变量=值:相当于给上级作用域(一般都是window),设置了一个属性(以后再操作window下就有了)
alert(变量):需要输出这个变量,但是此时是没有的,所以此时会报错
!!!
//变量提升:var x;var y;fn=AAAFFF111 console.log(x,y) //undefined,undefined var x=10,y=20等价于 var x=10;var y=20 function fn(){ //[私有作用域] //变量提升:var x(x是私有变量) console.log(x,y) //x=undefined,20 var x=y=100 等价于 var x=100(私有);y=100(全局) console.log(x,y) //100,100 } fn() console.log(x,y) //10,100
3 . 只对等号左边的进行变量提升,右边是值,不会提前声明的
=:赋值,左边是变量,右边都是值
//变量提升:var fn; fn() //会报错,它是函数表达式 console.log(fn) //undefined var fn = function(){ } console.log(fn) //函数本身 fn() //不会报错 function fn(){}
真实项目中 推荐函数表达式方式(第一种)好的编码习惯
1:因为只能对等号左边进行提升,多余变量提升完成后,当前函数只是声明了,没有定义,想要执行函数只能放在赋值的代码之后,
(放在前面执行相当于让undefined执行,会报错的)
2.这样让我们的代码逻辑更加严谨,以后想要知道一个执行的函数做了什么功能,只需要向上查找定义的部分即可(不会存在定义的代码在执行下面的情况)
var fn = function sum(){ console.log(sum) //函数本身 }; sum() //会报错
给一个匿名函数添加一个sum名字 ,在fn作用域中会报错,但是在其私有作用域中不会报错
//匿名函数:函数表达式(把函数当做一个值赋值给变量或其他内容)oDiv.onclick=function(){}
4 .不管条件是否成立都要进行变量提升
不管条件是否成立,判断体中出现的var/function 都会进行变量提升,但是在最新版浏览器中,function生命的变量只能提前声明
不能定义了,所以是undefined(前提:函数是在判断体中)
console.log(num) //undefined console.log(fn) //undefined if(1==1){ var num =12 function fn(){} }
代码执行到条件判断地方
[条件不成立] :进入不到判断体当中,此时之前声明的变量或函数依然是undefined
[条件成立]:进入条件判断体中的第一件事情不是代码执行,而是把之前变量提升没有定义的函数首先定义了
(进入到判断体中函数就定义了:迎合es6中的块级作用域)此时打印是函数本身
老版本浏览器处理方式:不管条件是否成立,都要进行变量提升(和新版本不一样的地方,新版本function只是声明,老版本function依然是声明+定义)
5 .关于重名的处理
在变量提升阶段,如果名字重复了,不会重新的进行声明,但是会重新的进行定义(后面赋的值会把前面赋的值给替换掉)
6 .查找私有变量
js中的私有变量有且只有两种
①:在私有作用域变量提升阶段,声明过的变量/函数
②:形参也是私有变量
函数执行形成一个新的私有作用域的步骤
①:形参赋值
②:变量提升
③:代码自上而下执行
④:当前栈内存(私有作用域)销毁或者不销毁
function fn(b){ //[私有作用域] //1.形参赋值 b=1 //2.变量提升:b=aaafff111(此处赋值操作替换了形参赋值的内容) console.log(b); //? b(){console.log(b)} function b(){ console.log(b) //? b(){console.log(b)} } b(); } fn(1)
实参传递的都是值,形参都是变量
7.如何查找上级作用域
函数执行形成一个私有的作用域(A),A的上级作用域是谁,和他在哪执行的没关系,主要是看他在哪定义的,
在哪个作用域下定义的,当前A的上级作用域就是谁
var n =10 function sum(){ console.log(n) } function(){ var n =100 sum() // 10 sum的宿主环境是当前自执行函数形成的私有作用域 }()
8.闭包作用(保护)
形成私有作用域,保护里面的变量不受外界的干扰
Zepto:小型jQ,专门为移动端开发准备的
window既不是关键字也不是保留字
//jQuery代码片段 //采用闭包原理,保护里面的私有作用域 (function(window){ var jQuery = funtion(){ } window.jQuery = window.$ = jQuery })(window) jQuery () $() //Zepto代码片段 //采用闭包原理,保护里面的私有作用域 var Zepto = (function(window){ var Zepto = funtion(){ } return Zepto })(window) Zepto ()
项目中用到闭包是:多人协作开发,每个人建立一个私有函数,把代码放在私有作用域中
8.1 闭包作用(保存)
函数执行形成一个私有作用域,函数执行完成,形成的这个栈内存一般情况下就会自动释放,
但是还有二般情况:函数执行完成,当前私有作用域(栈内存)中的某一部分内容被栈内存以外的其他东西(变量/元素事件)占用了,当前的栈内存就不能释放掉,也就形成了不销毁的私有作用域(里面的私有变量也不会销毁)
i++和++i的区别
i++:先拿原有的i值和其它值进行运算,运算完成后在自身累加1;++1:先自身累加1,然后拿累加完成的结果和其它值进行运算
var i =5; console.log(5+i++) //10 == console.log(5+(i++)) //10
console.log(i) //7
函数执行形成一个私有作用域,如果私有作用域中的部分内容被以外的变量占用了,当前作用域不销毁
[形式]:函数执行返回了一个 引用数据类型堆内存的地址(并且堆内存隶属于这个作用域),在外面有一个变量接收了这个返回值,此时当前作用域就不能销毁(想要销毁,只需要让外面的变量赋值为null,也就是不占用当前作用域中的内容了)
四:this
当前函数执行的主体(谁执行函数this就是谁)
函数外面的this是window,我们一般研究函数内的this指向问题
!!! this是谁和他在哪定义的以及在哪执行的没有任何关系,就看他的执行主体是谁
在JS非严格模式下(默认模式是非严格模式)
箭头函数里面是没有this的,而普通函数是有this的
区别:普通函数在定义的时候并不知道自己的this要指向哪里,所以在被调用的时候普通函数里的this会指向调用它的那个对象
而箭头函数因为本身没有this,它会直接绑定到它的词法作用域内的this,也就是定义它时的作用域的this值,通俗点说:就是它会直接绑定到它父级的执行上下文里的this(看啥时候执行的上下文)
1.自执行函数中的this一般都是window
var obj = { fn:(function(){ //this=>window return function(){ }}) }
2.给元素的某个事件绑定方法,当事件触发执行对应方法时候,方法中的this一般都是当前操作的元素本身
oBox.onclick=function(){ //this->oBox }
3.还有一种方式可以快速区分this:当方法执行时候,看看方法前面是否有 '点',有点,点前面是谁this就是谁,没有 ‘点’ this一般都是window
在严格模式下,如果执行主体不明确,this指向的是undefined(非严格模式下指向的是window),自执行函数this是undefined
开启严格模式:在当前作用域的第一行加上 ‘use strict’ 开启严格模式,那么当前作用域下再执行JS代码都是按照严格模式处理的
总结:
闭包:函数执行,形成一个私有作用域保护里面的私有变量不受外界的干扰,这种保护机制叫做 “闭包”
但是现在市面上,函数执行,形成一个不销毁的私有作用域,除了保护私有变量以为,还可以存储一些内容,这样的模式才是闭包
var utils = (function(){ return { } })() //典型的闭包
作用:1.保护:①:团队协作开发,每个开发者把自己的代码存放在一个私有的作用域中,防止相互之间的冲突;把需要供别人使用的方法,通过return或者window.XXX暴露在全局下即可
②:jQuery源码中也是利用保护机制实现的
2:保存:①:单例模式(JS高阶编程技巧:惰性思想/柯理化函数思想)
作用域
栈内存:全局作用域:window;
私有作用域:函数执行 ;
块级作用域:使用let创建变量存在块级作用域;
作用域链:当前作用域代码执行的时候遇到一个变量,我们首先看一下它是否属于私有变量,如果是当前作用域的私有变量,那么
以后在私有作用域中再遇到这个变量都是操作私有的(闭包:私有作用域保护私有变量不受外界干扰),如果不是私有变量,
向其上级作用域查找,也不是上级作用域私有的,继续向上查找,一直到window全局作用域为止,我们把这种向上一级级查找的
机制叫做作用域链;全局下有,操作的就是全局变量,全局下没有(设置:给全局对象window增加了属性名 获取:报错)
声明式VS编程式
for循环属于编程式 能看出如果实现的
操作数组的方法
1. forEach 不支持return 属于声明式(不关心如何实现)
for in 循环在遍历的时候,默认的是可以把自己私有的和它在所属类原型上扩展的属性和方法都可以遍历到,但是一般情况下,我们遍历一个对象只需要遍历私有的即可,我们可以使用以下的判断进行处理
if(obj.hasOwnProperty(key)){ console.log(key) }
for .. in 、for...of、forEach的区别
//key会变成字符串类型(string),包括数组的私有属性也可以打印出来,这里是key值不是value for(let key in arr){ console.log(typeof key) //string } //支持return(不能遍历对象) 这里是value for(let val of arr){ console.log(val) } //不支持return arr.forEach(function(item){ console.log(item) })
2. filter 不会改变原数组,返回的结果是过滤后的新数组,回调函数中如果返回true表示这一项放到新数组中 (适用删除某项)
3.map 映射 将原有的数组映射成一个新的数组 返回是新数组,回调函数中的返回什么这一项就是什么 (适用更新)
let arr = [1,2,3].map(function(item){ return item*2 //[2,4,,6] })
4.includes 返回的是Boolean,
5.find 返回找到的那一项,不会改变数组,回调函数返回true表示找到了,找到后就会停止循环,找不到返回undefined
let result = arr.find(function(item,index){return item })
6.some 找true 找到true后停止,返回true,找不到返回false
7.every 找false 找到false后停止 返回false
8. reduce
//reduce 返回的是叠加的结果 回调函数返回的是 原数组不变 var arr = [1,2,3,4,5] //参数分别是 上一个,下一个,索引,当前数组 // 数组求和 let sum = arr.reduce(function(prev,next,index,item){ return prev+next //本次的返回值会作为下一次的 prev }) let sum = arr.reduce(function(prev,next,index,item){ },0) //默认指定第一次的prev //扁平化数组 var arr = [[1,2,3],[4,5,6]].reduce(function(prev,next){ return prev.concat(next) }) console.log(arr)
【知识点】:
在私有作用域中声明的变量都是私有变量,在全局作用域中声明的都是全局变量
把类数组转化为数组 Array.prototype.slice.call(arguments)
如果 end 未被规定,那么 slice() 方法会选取从 start 到数组结尾的所有元素。也就是克隆元素
//参数求和 function fn(){ var ary = Array.prototype.slice.call(arguments) return eval(ary.join('+')) }
JS代码运行在浏览器中,是因为浏览器给我们提供了一个供js代码执行的环境-》全局作用域
前端的作用域(window)后台开发node(global)