一.数据类型的操作原理
-
数据类型
-
基本数据类型(值类型)
- number
- string
- boolean
- null
- undefined
-
引用数据类型
-
对象
- {} 普通对象
- /^$/正则
- 一般类的实例都是对象数据类型的
- 函数的prototype属性
- 实例的proto属性
-
函数
- function 普通函数
- 类
- 。。。
console.log(typeof Object);//function
-
-
值类型
直接按值操作,例如:var a=12;直接把12这个赋值给变量a(让a变量和12这个值建立了链接关系)
-
对象数据类型
在js中遇到对象,会严格按照如下的步骤操作
1.浏览器为其开辟一个新的内存空间,为了方便后期可以找到这个空间,浏览器给空间分配一个16进 制的地址
2.按照一定顺序,分别的把对象键值对存储到内存空间中
3.把开辟内存的地址赋值给变量(或者其他的东西),以后变量就可以通过地址找到内存空间,然后进行一些操作
- 分析例子
js代码运行在浏览器中,是因为浏览器给我们提供了一个供js代码执行的环境->全局作用域(window/global)
var a=12; var b=a; b=13; console.log(a); var o={name:"珠峰培训"}; var p=o; p.name="周啸天" console.log(o.name); var m={name:"珠峰培训"}; var n=m; n={name:"中国最权威的前端培训机构"}; console.log(m.name);
-
-
函数的操作
-
创建函数
1.先开辟一个新的内存空间(为其分配了一个16进制的地址)
2.把函数体中编写的js代码当做字符串存储到空间中(函数只创建不执行没有意义)
3.把分配的地址赋值给声明的函数名(function fn和var fn操作原理其实相同,都是在当前作用域声明了一个名字)
-
执行函数
目的:执行函数体中的代码
1.函数执行的时候,浏览器会形成一个新的私有作用域(只能执行函数体 中的代码)供函数体中的代 码执行
2.执行代码之前,先把创建函数存储的那些字符串变为真正的js表达式, 按照从上到下的顺序在私有作用域中执行,一函数可以被执行N次,
每一次的执行相互之间互不干扰形成的私有作用域把函数体中的私有变量都包裹起来了(保护起来了),在私有作用域中操作私有 变量和外界没关系,外界也无法直接的操作私有变量
3.函数执行具体步骤
- 形参赋值
- 变量提升
- 代码自上而下执行
- 当前栈内存(私有作用域)销毁 (也有例外:不销毁的时候,往下看闭包)
案例分析
var a=12; var b=a; b=13; console.log(a); var o={name:"珠峰培训"}; var p=o; p.name="周啸天" console.log(o.name); var m={name:"珠峰培训"}; var n=m; n={name:"中国最权威的前端培训机构"}; console.log(m.name); function fn(){ var ary=Array.prototype.slice.call(arguments); return eval(ary.join('+')); } fn(12); fn(13);
-
二.堆栈内存
-
栈内存
俗称叫做作用域(全局作用域/私有作用域)
- 为js代码提供执行环境(执行js代码的地方)
- 基本数据类型值是直接存放在栈内存中的
-
堆内存
存储引用数据类型值的(相当于一个存储的仓库)
- 对象存储的是键值对
- 函数存储的是代码字符串
-
内存释放
在项目中,我们使用的内存越少性能越好,我们需要把一些没用的内存处理掉
-
堆内存
var o={};当前对象对应的堆内存被变量o占用着呢,堆内存是无法销毁的
o=null;null空对象指针(不指向任何的堆内存),此时上一次的堆内存就没有被占用了,谷歌浏览器会在空闲时间把没有被占用的堆内存自动释放(销毁/回收)
-
栈内存
一般情况下,函数执行形成的栈内存,函数执行完,浏览器会把形成的栈内存自动释放;
有的时候执行完成,栈内存不能被释放?
全局作用域在加载页面的时候执行,在关掉页面的时候销毁
-
三.变量提升
-
定义及案例分析
- 定义
在当前作用域中,js代码自上而下执行之前,浏览器首先会把所有带
var/funciton
关键字的进行提前的声明或者定义,也就是该变量不管是在当前作用域的哪个地方声明的,都会提升到所属作用域的最顶上去
声明(declare):var num ;在当前作用域中吼一嗓子我有num这个名了
定义(defined): num=12;把声明的名字赋一个值
带var关键字的只是提前的声明一下;
带funciton关键字的在变量提升阶段把声明和定义都完成了
同一个变量只会声明一次,其他的会被忽略掉
函数声明的优先级高于变量申明的优先级
- 分析这一段代码
console.log(num); console.log(fn); var num=13; function fn(){ console.log(a); var a=10; console.log(a); } fn(); console.log(num);
-
不管条件是否成立都要进行变量提升
不管条件是否成立,判断体中出现的var/function,都会进行变量提升;
但是在最新版浏览器中,function声明的变量只能提前声明不能定义了(前提:函数是在判断体中)
console.log(num);//undefined console.log(fn);//undefined if(1==1){ console.log(num);//undefined console.log(fn);//fn函数本身 var num =12; function fn(){ } } console.log(fn);//fn函数本身
代码执行到条件判断的地方
-
条件不成立
进入不到判断体中,赋值的代码执行不了,此时之前的声明的变量或者函数以然是undefined
-
条件成立
进入到条件判断体中的第一件事情不是代码执行,而是把之前的变量提升没有定义的函数首先定义了(进入到判断体中的函数就有定义了)
老版本浏览器不是这样处理的:不管条件是否成立,function都要进行变量提升(声明+定义)
新版浏览器function只是声明
-
-
关于重名的处理
//重名的只会声明一次,函数在提升阶段就可以定义了就是赋值,可以多次赋值 //=>变量提升:fn=aaafff111 (=aaafff222) (=aaafff333) (=aaafff444) fn();//4 function fn(){console.log(1);} fn();//4 function fn(){console.log(2);} fn();//4 var fn=13; fn();//Uncaught TypeError: fn is not a function function fn(){console.log(3);} fn(); function fn(){console.log(4);}
-
变量提升,函数优先
//函数声明和变量声明都会被提升,但是需要注意的是函数会先被提升,然后才是变量 var fn1; //只定义没有赋值,意味着原有栈中fn1的结果没有改变 function fn1(){ } console.log(typeof fn1);//function 理由:var fn1;尽管出现在function fn1()之前,但它是重复的声明,会被忽略,因为函数声明会被提升到普通变量之前 ( 其实也可以从另外一个角度去理解,提升变量声明fn1, 但是函数声明和定义都被提升上去的,最总fn1赋值是函数体 ) var fn1=4;//定义赋值,覆盖函数 function fn1(){ } console.log(typeof fn1);//number
四.作用域/作用域链
-
定义
-
作用域
【栈内存】- 全局作用域:window
- 私有作用域:函数的执行
- 块级作用域:使用let创建变量存在块级作用域
-
作用域链
当前作用域代码执行的时候遇到一个变量,首先看一下它是否属于私有变量,
如果不是私有变量,向其上级作用域查找,也不是上级作用域私有变量,继续向上查找,
一直到window全局作用域为止,
我们把这种向上一级级查找机制叫做作用域链
-
-
查找私有变量
js中的私有变量有且只有两种
在私有作用域变量提升阶段,声明过的变量(或者函数)
形参也是私有变量
-
案例分析
- 案例1
- 案例2
function fn(b){ //=>私有作用域 //=>形参赋值:b=1(私有变量) //=>变量提升:b=aaaff111 (此处赋值操作替换了形参赋值的内容) console.log(b);//=>函数 function b(){ //=>私有作用域 //=>形参赋值和变量提升都没有 console.log(b);//=>函数,不是私有变量,去上一级作用域查找, } b(); } fn(1);
-
如何查找上级作用域
函数执行形成的一个私有的作用域(A),A的上级作用域是谁,和它在哪执行的没有关系,主要看他在哪定义的,在哪个作用域下定义的,当前A的上级作用域就是谁;
- 例1
var n=10; function sum(){ console.log(n);//10 } (function(){ var n=100; sum(); })();
- 例2
var n=10; var obj={ n:20, fn:(function(){ var n=30; return function(){ console.log(n);//30 } })(), }; obj.fn();
- 例3
var n=10; var obj={ n:20, fn:(function(){ //上级作用域:全局作用域 return function(){ //上级作用域:自执行函数 console.log(n);//10 } })(), }; obj.fn();
- 例4
var n=10; var obj={ n:20, fn:(function(n){ return function(){ console.log(n);//10 } })(obj.n) //Uncaught TypeError: Cannot read property 'n' of undefined //因为这时候obj还没有呢 }; obj.fn();
五.闭包
-
闭包作用
-
保护作用
形成私有作用域,保护里面的私有变量不受外界的干扰
例如jqery代码
(function(){ var jQuery=function(){ 。。。 } window.jQuery=window.$=jQuery; })(); jQuery();
-
保存作用
函数执行形成一个私有作用域,函数执行完成,形成的这个栈内存一般情况下都会自动释放
但是还有二般情况:函数执行完成,当前私有作用域(栈内存)中的某一部分内容被栈内存以外的其他东西占用了,当前的栈内存就不能释放掉,也就形成了不销毁的私有作用域(里面得到私有变量也不会销毁)
【形式】
函数执行返回了一个引用数据类型堆内存的地址,在外面有一个变量接收了这个返回值,此时 当前作用域就不能销毁,(想要销毁,只需要让外面的变量赋值为null,也就是不占用当前 作用域的内容了)
function fn(){ var i=1; return function(n){ console.log(n+ i++); } } var f=fn(); //只要f不销毁,fn函数形成的私有作用域不会销毁 f(10);//11 fn()(10);//11 //这种执行完,fn函数形成的私有作用域就销毁了 f(20);//22 fn()(20);//21
-
六.原型
-
1.原理
所有的函数都天生自带一个属性:prototype(原型),它是一个对象数据类型的值,在当前prototype对象中,存储了类需要给其实例使用的公有的属性和方法
prototype这个对象,浏览器会默认为其开一个堆内存,这个堆内存中天生自带一个属性:constructor(构造函数),这个属性存储的的值就是当前函数本身
每一个类的实例(每一个对象)都天生自带一个属性:proto,属性值是当前对象所属类的原型(prototype)
分析个例子
function Fn(name,age){ //这里面的属性都是私有的 this.name=name; this.age=age; this.say=function(){ console.log(this.name); } } //原型上的都是公有的 Fn.prototype.say=function(){ console.log("hello world"); } Fn.prototype.eat=function(){ console.log("I like food"); } var f1=new Fn("王燕燕",19); var f2=new Fn("王雪超",69); f1.eat();
-
2.内置类的原型分析
/* 判断是否是对象的对象属性 hasOwnProperty 私有和公有是一个相对论,我们需要看相对于哪个对象而言: 1.相对于实例ary1来说push是公有的 2.相对于Array.prototype来说,push就是自己的私有的 3.凡是通过__proto__找到的属性都是公有的,反之都是私有的 */ var ary1=[12,23]; console.log(ary1.hasOwnProperty("push"));//false console.log(Array.prototype.hasOwnProperty("push"));
-
3重新构造原型
function Fn(name){ this.name=name; this.say=function(){ console.log(this.name); } } Fn.prototype={ /*让原型指向自己开辟的堆内存有一个问题:自己开辟的堆内存中没有constructor这个属性, 所以实例在调取constructor的时候找到的是Object,这样不好, 此时我们应该重新设置一下constructor,保证机制的完整性 */ constructor:Fn, aa:function(){}, bb:function(){} } var f =new Fn("wanglei"); console.log(f.constructor)//Fn函数
7.继承
-
原型继承
让父类中的属性和方法在子类实例的原型链上
-
特点
它是把父类的原型放到子类实例的原型链上,实例想调取这些方法,是基于
__proto__
原型链查找机制完成的子类可以重写父类上的方法(这样会导致父类其它的实例也会收到影响)
父类中私有或者公有的属性方法,最后都会变为子类中公有的属性和方法
代码分析
function A(x){ this.x=x; } A.prototype.getX=function(){ console.log(this.x); } function B(y){ this.y=y; } //这2行代码就实现了原型继承 B.prototype =new A(200); B.prototype.constructor=B; B.prototype.getY=function(){ console.log(this.y); } var b=new B(100); console.log(b);
-
-
call继承
-
特点
child方法中把parent当做普通函数执行,让parent中的this指向child的实例,相当于给child的实例设置了很多私有的属性或者方法
只能继承父类私有的属性或者方法(因为是把parent当做普通函数执行,和其原型上的属性和方法没有关系)
父类私有的变为子类私有的
代码
function A(x){ this.x=x; } A.prototype.getX=function(){ console.log(this.x); } function B(y){ //这行代码实现了call继承 A.call(this,200);//=>b.x=200 this.y=y; } B.prototype.getY=function(){ console.log(this.y); } var b=new B(100); console.log(b);
-
-
寄生组合继承:call继承+类似原型继承 (推荐)
特点
父类私有和公有的分别是子类实例的私有和公有属性方法代码分析
function A(){ this.x=10; } A.prototype.getX=function(){ console.log(this.x); } function B(){ //这行代码继承父类私有属性 A.call(this); this.y=10; } //这2行代码继承父类原型上的属性(公有属性) B.prototype = Object.create(A.prototype); B.prototype.constructor=B; B.prototype.getY=function(){ console.log(this.y); } var b=new B(); console.log(b);