JS(二):变量、作用域

一、复习


变量:可修改的保存数据的容器。
变量的命名:$ _ 字母 数字,只有数字不能开头,关键字保留字也不能开头。
jQuery:$.each();
underscore:_.each();//这也是一个js的函数库。
$ == jQuery;
区分大小写。
关键字:if,for等。
保留字:class等。
关于保留字及关键字的总结,可以参考文档资料

命名要有意义,单词,可以用下划线或驼峰法连接。
i/j/k用于计数循环。
--------
变量的声明:两种方式。
第一种:通过var声明。(是大多数情况)
第二种:不用var。(以后再讨论)

二、变量

(一)数据类型和堆栈

1.基本类型:String Number Boolean Null Undefined
不能修改,只能被覆盖,存储在栈中,变量保存的是值本身,栈是有序的
所有的字符串的方法不是在原有的字符串基础上进行修改的而都是会返回一个新的字符串
例如:类似str.replace()返回修改后的字符串,不影响修改前的

2.引用类型:对象{} 数组[]
引用类型保存在堆内存中,保存引用类型的变量保存的并不是对象本身,而是一个指向该对象的引用地址。

注:基本类型按值访问,引用类型按引用访问
保存基本类型的变量保存的是值本身。

堆栈
(1).栈的大小是固定点的,排列是有序的,保存基本数据类型。
(2).堆的大小是不固定的,排列是无序的,引用的地址保存在堆中,通过地址访问堆中的数据。

(二)变量比较和值的复制

1.基本类型的值的复制:

var xm = 2;
var xh = xm;
xm++;
console.log(xm === xh);
console.log(xm); //5
console.log(xh); //4

 

2.引用类型的值的复制:

var xm = { age: 18, score: 4 }
var xh = xm;
xm.score++;
console.log(xh.score); //5
console.log(xm.score);//5

引用类型对象赋值比较:

var xm={age:18, score:4};
var xh=xm;
function equalObjs(a,b){
for(var p in a){
if(a[p]!==b[p])return false;
}
return ture;
}
console.log(equalObjs(xm,xh));//true

.创建一个独立的对象,改变值不会受影响的那种(引用类型的复制,值不受影响):

 

var xm={ age:18,score:4};
function copyObj(obj){
var newObj = {};
for(var p in obj){
newObj[p] = obj[p];
}
return newObj;
}
xh = copyObj(xm);
console.log(xh === xm); //false 这是两个对象 浅拷贝


//这里是浅拷贝,功能不完善,里面其实是基本类型的复制,如果再来一个引用类型,这样直接赋值会都指向同一个引用类型。如果能将赋值的引用类型也区分开,那就是深拷贝。

$.extend()即可浅拷贝,又可深拷贝。深拷贝跟递归有关。

注意:

console.log([ [] == [] , [] === [] , 4 = [4] , 4 === [4] ])
[false,false,true,false]
var a=[4];
var b=a;
a=[4,44];
console.log(b);
a=b;
b.push(44);
console.log(a);
解析:[4] [4,44]
从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同一个对象,当给变量赋新值时,此变量就不再指向原来的对象了;
数组是引用类型的 , 如题中b=a相当于a与b指向同一个地址 , 但是a=[4,44]之后改变了a的地址 , 所以b不会受到影响 为[4] . 后面又把a=b指向了同一个地址 ,此时为b添加元素 , 因为a、b的地址都没有改变 ,a也会随之改变 ,所以a为[4,44] .

(三)参数传递和类型检测

1.参数传递

基本类型:

function fn(a,b){
return a+b;
}
fn(1,2);//基本类型是按值传递;

引用类型:

function setName(obj){
return obj.name="xm"

}
var preson={};
setName(preson);
console.log(person.name)//xiaoming;这里是把引用地址传递给这个函数,同时是按值访问;

例如:

function setName(obj){
    obj.name='xm';
    obj={};
    obj.name='xh';
}
var person={};
setName(person);
console.log(person.name);//'xm'

js中所有参数的传递都是按值来传递的,不论是基本型数据还是引用型数据。
这里对引用类型person对象传参,也是按值传递,相当于obj=person

注意:引用型数据变量在重新赋值之后(一定是赋值,而不是其它更改数据操作),哪怕是用空对象赋值,这个新赋的值就开辟了一个新的对象,变量就已经重新指向新数据,就不再是对原来数据的引用了。
这里的obj={}之后,obj就指向一个新数据了,不再是原来跟person指向同一个数据了。

2.类型检测

typeof //对数组和对象的检测只能返回object,如果要详细检测,只能使用instanceof。
console.log(typeof 4);//number
console.log(typeof(4));//number
console.log(typeof 'str');//string
console.log(typeof true);//boolean
console.log(typeof undefined);//undefined
console.log(typeof null);//Object
console.log(typeof []);//Object
console.log(typeof {});//Object
console.log(typeof function () {});//function
console.log(typeof /a/);//Object,这个在某些浏览器会显示function
-------
instanceof(含义:前面的是后面的实例)只能和引用类型数据连用,基本类型数据使用都会返回false。
console.log([] instanceof Array);//true
console.log([] instanceof Object);//true
console.log({} instanceof Object);//true
console.log({} instanceof Array);//false
console.log( 1 instanceof Number );//false

三、作用域与解析机制

(一)全局作用域和局部作用域

1.变量的生命周期。

2.哪里可以访问到变量。

作用域: js中的局部作用域也就是函数作用域,函数体内部的执行环境。

因为js中是没有块级作用域{}的,if和for后面的{}里声明的变量都是全局变量。块级作用域是只在块内有效,出了块区域就不能访问了。

全局变量:当所有的程序执行完毕后,整个全局作用域被销毁才会清除全局变量。

局部变量:函数执行时才能存活,函数一旦执行完毕就销毁。

不用var声明的变量是全局变量,y是全局变量,可以输出;x是局部变量,因此输出x会报错。
例如:function fn(){
var x=y=1;
}
外部可以访问到y,而不能访问到x。

(二)变量对象和作用域链


1.window是全局作用域的变量对象。
2.不存在的变量会报错--person,不存在的属性汇报undefined--window.person。

var name='xm';
function fn(argument){
    var sex='male';
    function fn2(argument){
        var age=18;
    }
}

console.log(window.name===name);//true;
console.log(window.fn===fn);//true;
console.log(person);//undefined


3.作用域链:查找变量的方法
多个作用域形成作用域链,局部作用域到全局作用域,如果当前作用域没有找到变量,则返回上一层查找
4.同级作用域,内层作用域优先级最高,只能从外层查找。局部变量优于全部变量,更快。

var name='xm';
function fn(){
    var name='xh';
    var sex='male';
    function fn2(){
        var name='xhei';
        var age=18;
    }
}

fn2里查找name时:先找里层的,没找到再往外走。内层能查找外层,外层不能查找内层。

5.延长作用域链

使用with可以延长作用域链,不建议使用

var person={};
person.name="xm";
person.sex="male";
var score=4;
console.log(person.name);//"xm"
console.log(person.sex);//"male"
console.log(score);//4

with(person){//相当于修改person
    name=“xh”,
    sex="female";
    score=500;
}
console.log(person.name);//"xh"
console.log(person.sex);//"female"
console.log(score);//500

这里的score属性也被修改,是因为with的原因,函数要向上面的作用域链上查找变量;

例题:

console.log(window.person||(window.person='xm'));
console.log(pereson1 ||(person1='xm'));

xm,报错
1,不存在的变量或函数会报错;不存在的属性或方法,返回undefined;
2,||是短路操作,形如a||b,如果a的值转换成布尔值是true的话,a||b等于a;如果a的值转换成布尔值是false的话,a||b等于b。

(三)JS解析机制-预解析

1,预解析
2,再逐行解读代码

var name='xm';
var age=18;
function fn(argument){
    console.log(name);
    var name='xh';
    var age=10;
}


--预解析过程:
会分别在不同作用域内进行预解析,先看全局作用域。
先查找全局作用域中所有的var(注意:没有用var声明的变量是不会被预解析的哦,以let 声明的变量也不会进行预解析哦 ),将后面的变量赋值给undefined(这也是为什么如果声明变量不赋值,变量默认是undefined),再查找全局作用域内的function关键字,然后把函数的内容整个都声明。再在局部作用域中查找var,也是把变量赋值undefined。有function也是把函数整个拿过来声明。函数表达式不会进行预解析。

--逐行读代码:
一行一行读代码,把变量赋相应的值,函数在预解析过程中已经声明完毕,不会再读,直接跳到函数执行的代码,然后进入函数内部执行代码。这里fu函数中打印name,因为在预解析过程中,fn中的name是赋值undefined的,且代码还没有执行到var name = 'xh',所以打印出来的name为undefined。注意:因为函数内部有变量name,所以不会向上查找去全局作用域中找name,所以打印结果为函数内部的name在预解析过程中赋值的undefined。

3,在预解析过程中如果碰到冲突问题:
--1.变量跟变量同名冲突。
在预解析的时候,它们都是undefined;在执行过程中,变量的值由最后声明的代码决定。

--2.变量和函数同名冲突。
在预解析时,同名的变量就不存在了,只保留同名的函数,函数的优先级比变量高。

--3.函数和函数同名冲突。
在预解析时,两个都是函数,优先级一样,所以最后声明的函数保留,以最后声明的同名函数为准。
4,关于预解析要注意
在有些老版本浏览器中,比如火狐的老版本浏览器中,它无法解析像if(){}或者for(){}这样的代码块里面的函数。也就是说如果你在if(){}或者for(){}这种代码块里声明函数,浏览器没有办法正常预解析,所以不推荐在这种代码块中定义函数,函数要么就放在函数中,要么就放到全局中,要么就放到变量中当方法。要不然很容易发生错误,并且不好找。

(四)JS解析机制详解

 1.JS作用域问题一
 ①

console.log(a);//undefined
var a = 1;

 ②

console.log(a);//报错
a = 1;

 2.JS作用域问题二
 ①

console.log(a);//a()
var a = 1;
console.log(a);//1
function a() {
     console.log(2);
}
console.log(a);//1
var a = 3;
console.log(a);//3
function a() {
    console.log(4);
}
console.log(a);//3
a();//报错

预解析
function a() {
 console.log(4);
 }

 3.JS作用域问题三
 ①预解析是分标签进行的。一个script标签内部解析完,再解析下一个script标签。

报错:

<script>
    console.log(a);
</script>
<script>
    var a=1;
</script>

不报错:

<script>
    var a=1;
</script>
<script>
    console.log(a);//1
</script>

 4.JS作用域问题四
 ①
 

var a =1;
function fn() {
     console.log(a);//undefined
    var a =2;
}
fn();
console.log(a);//1

 ②


var a = 1;
function fn() {
    console.log(a);//1
    a = 2;
}
fn();
console.log(a);//2

 ③

 var a = 1;
 function fn(a) {
     console.log(a);//undefined
     a = 2;
 }
 fn();
 console.log(a);//1

var a = 1;
function fn(a) {
     console.log(a);//1
     a = 2;
}
fn(a);
console.log(a);//1

四、内存管理与垃圾收集机制

(一)垃圾收集机制

垃圾收集机制:
程序中会运用到许多数据,数据在内存中都会占用一定的空间,没用的就是垃圾,还会继续驻留在内存中,内存被占用,可用的内存空间就会越来越小,耗尽内存,系统就奔溃了。所以要收集垃圾,释放无用的数据,回收内存。

1.自动收集:JS
2.手动收集:Objective-C

原理:找到没用的数据,打上标记,释放其内存。按一定的时间间隔周期性执行。(不同的浏览都是按这个步骤周期执行,不同的只是执行周期不同)

标识无用数据的策略(打上标记):
1.标记清除:垃圾收集器在运行时,会给存储在内存中的所有变量一次性全部加上标记,之后,去掉环境中的变量(变量还未离开它的执行环境),以及被环境中的变量所引用的变量的标记,剩下被标记的就是已经离开环境的变量(函数中的变量在函数执行完毕后就离开了环境),将会被清除。(几乎所有浏览器的js垃圾收集都是使用这一方式)
2.引用计数:(大多数浏览器放弃了,循环引用会出现问题,你中有我,我中有你,ie浏览器678中dom和bom由c++的COM基础上实现,不是原生的,引用的是引用计数的方式)
解决循环引用:将引用的属性或变量设置为null;次数将变为0,就清除了。

var xm={
    name:'xm',
    age:18
};//1
var xh=xm;//2
xh={};//1
xm={};//0

循环引用:

function fn(argument){
    var xm={};//1
    var xh={};//1
}
fn();
xm=null;//0
xh=null;//0


function fn(argument){
    var xm={};//1
    var xh={};//1
    xm.wife=xh;//2
    xh.husband=xm;//2
}
fn();
xm=null;//1
xh=null;//1

(二)管理内存

分配给web浏览器的内存 < 分配给桌面应用程序的内存。

优化内存最好的方式就是只保留内存中有用的数据,没有用的立马释放。

解除引用:设置为null;适用于大多数全局变量。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值