数据类型

基本类型值包括: undefined,null,Boolean,Number和String,这些类型分别在内存中占有固定的大小空间,它们的值保存在栈空间,我们通过按值来访问的。
引用类型包括:对象、数组、函数等。
对于引用类型的值,则必须在堆内存中为这个值分配空间。由于引用类型值的大小不固定(对象有很多属性和方法,而且还可以动态的添加属性和方法),因此不能把他们保存到栈内存中。但内存地址大小是固定的,因此可以将内存地址保存在栈内存中。

简而言之:栈内存中存放的是基本数据类型值,堆内存中存放的是引用类型值,引用类型值在内存中的地址存放在栈中,也就是我们常说的对象对象引用(指针)。

变量复制

<script type="text/javascript">
var a = 5;
var b = a;
console.log(a+"---"+b);//5---5
b = 6;//这里重新给b赋值,a值并没有改变
console.log(a+"---"+b);//5---6 var obj = {name:"lisi"};
var obj2 = obj;//这里是引用赋值,obj和obj2指向同一个对象
console.log(obj.name + "---" + obj2.name);//lisi---lisi
obj2.name = "wangwu";
console.log(obj.name + "---" + obj2.name);//wangwu---wangwu
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

从上面例子可以看出:在变量复制方面,基本类型和引用类型也有所不同,基本类型复制的是值本身,而引用类型复制的是内存地址。


函数

this 的工作原理(5种情况)

全局作用域内

当在全部作用域内使用 this时,它将会指向全局对象,即window对象

函数调用

挡在全局作用域内调用函数时,this 也会指向全局对象。

方法调用

this 指向调用该方法的对象。

调用构造函数

在构造函数内部,this 指向新创建的对象。

显式的设置this指向

当使用call 或者 apply 方法时,函数内的 this 将会被显式设置为函数调用的第一个参数。
更详细用法请见之前的博客:传送门

匿名函数

在JavaScript里任何匿名函数都是属于window对象。在定义匿名函数时候它会返回自己的内存地址,如果此时有个变量接收了这个内存地址,那么匿名函数就能在程序里被使用了(可以通过在这个变量后面加一对圆括号来调用这个匿名函数),因为匿名函数也是在全局执行环境构造时候定义和赋值,所以匿名函数的this指向也是window对象。

<script type="text/javascript">
(function(){
console.log(this == window);//true
})();
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

参数传递

<script type="text/javascript">
function test(num){//按值传递
num+=5;
console.log(num);
return num;
}
var num = 5;
var result = test(num);
console.log(result);// 10 如果是按引用传递,那么函数里的num会成为类似全局变量,把外面的num覆盖掉
console.log(num);// 5 也就是说,最后应该输出20(这里输出10)
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

js中不存在引用传递,如果存在引用传递的话,那么函数内的变量将是全局变量,在外部也可以访问,但这明显是不可能的。
再看一个例子:

<script type="text/javascript">
function setName(obj){//obj = person
obj.name = "lisi";
obj = new Object();
obj.name = "wangwu";
}
var person = new Object();
setName(person);
alert(person.name);
console.log(obj.name);//Uncaught ReferenceError: obj is not defined
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在将person传递给obj后,其name属性就被设置成了”lisi”。又将obj重新定义了一个对象,另一行代码为该对象定义了一个带有不同值的name属性。
如果person是按引用传递的,那么person就会自动被修改为指向其name属性值为”wangwu”的新对象,但事实上并没有,其name属性依然是”lisi”。
这就说明:即使在函数内部修改了参数的值,但原始的引用仍然保持不变。实际上,当在函数内重写obj时,这个变量引用的就是一个局部对象了。而这个局部对象会在函数执行完毕后立即被销毁。

执行环境及作用域

执行环境

执行环境定义了变量或函数有权访问其他数据,可以分为全局执行环境和局部执行环境。
全局执行环境是最外层的执行环境,在浏览器中,全局执行环境是window对象,因此,所有的全局变量的函数都是作为window的属性和方法创建的。
注意:对于局部执行环境,其内部的代码执行完毕后,该环境将被销毁,保存其中的变量和函数也随之销毁,如果是全局执行环境,需所有程序执行完毕或网页完毕后才会销毁。

需要注意的小细节

去掉var的局部变量
<script type="text/javascript">
var name = "wangwu";
function setName(){
name = "lisi";//去掉var变成了全局变量,会覆盖全局中的name
}
setName();
console.log(name);//lisi
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
形参也是局部变量
<script type="text/javascript">
var name = "wangwu";
function setName(name){//形参也是局部变量
console.log(name);
}
setName("lisi");//lisi
console.log(name);//wangwu
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

作用域

当代码在一个环境中执行的时候,就会形成作用域链,它的作用是保证对执行环境中有访问权限的变量和函数进行有序访问(内部深层环境可以访问外层环境,反之不成立),作用域链的前端,就是执行环境的变量对象。
详见之前博客:传送门

内存泄漏常见情况

javascript具有自动垃圾回收机制,一旦数据不再使用,可以将其设为”null”来释放引用。

循环引用

分为两种情况:不同对象之间相互引用和
一个经典的例子:一个DOM对象被一个Javascript对象引用,与此同时又引用同一个或其它的Javascript对象,这个DOM对象可能会引发内存泄露。这个DOM对象的引用将不会在脚本停止的时候被垃圾回收器回收。要想破坏循环引用,引用DOM元素的对象或DOM对象的引用需要被赋值为null。

<body>
<script type="text/javascript">
var oBox = document.getElementById("box");
var obj = {};
oBox.name = obj;
obj.age = oBox;//这样就发生了循环引用
</script>
</body>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

闭包(常见)

通过闭包引用其包含函数的局部变量,当闭包结束后该局部变量无法被垃圾回收机制回收,造成内存泄漏。

DOM泄漏

当原有的DOM被移除时,子结点引用没有被移除则无法回收。

var select = document.querySelector;
var treeRef = select('#tree');
//在COM树中leafRef是treeFre的一个子结点
var leafRef = select('#leaf');
var body = select('body');
body.removeChild(treeRef);
//#tree不能被回收入,因为treeRef还在
//解决方法:
treeRef = null;
//tree还不能被回收,因为叶子结果leafRef还在
leafRef = null;
//现在#tree可以被释放了。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

定时器泄漏

for (var i = 0; i < 1000; i++) {
var obj = {
fn: function() {
var that = this;
var val = setTimeout(function() {
that.fn();
}, 500);
}
}
obj.fn();
//虽然你想回收但是timer还在
obj = null;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

垃圾回收

标记清除

js中最常用的垃圾回收方式就是标记清除。垃圾收集器在运行的时候回给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记(闭包)。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾收集器完成内存清除工作,消除那些带标记的值并回收它们所占用的内存空间。
例如:在函数中声明一个变量,就将这个变量标记为”进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为”离开环境”。

function test(){
var x = 10 ; //被标记 ,进入环境
var y = 20 ; //被标记 ,进入环境
}
test(); //执行完毕后 x、y又被标离开环境,被回收。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

引用计数

引用计数就是:跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾回收器下次再运行时,它就会释放那些引用次数为0的值所占用的内存。

function test(){
var x = {} ; //x的引用次数为0
var y = x ; //x的引用次数加1,为1
var z =x; //x的引用次数再加1,为2
var y ={}; //x的引用次数减1,为1
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
引用计数策略的问题(循环引用)
function test() {
var a = {};
var b = {};
a.name = b;
b.name = a;
}
test();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

以上代码a和b的引用次数都是2,fn()执行完毕后,两个对象都已经离开环境,在标记清除方式下是没有问题的,但是在引用计数策略下,因为a和b的引用次数不为0,所以不会被垃圾回收器回收内存,如果fn函数被大量调用,就会造成内存泄露。在IE7与IE8上,内存直线上升。
我们知道,IE中有一部分对象并不是原生js对象。例如,其BOM和DOM中的对象就是使用C++以COM对象的形式实现的,而COM对象的垃圾回收机制采用的就是引用计数策略。因此,即使IE的js引擎采用标记清除策略来实现,但js访问的COM对象依然是基于引用计数策略的。换句话说,只要在IE中涉及COM对象,就会存在循环引用的问题。

var element = document.getElementById("some_element");
var myObject = new Object();
myObject.element = element;
element.someObject = myObject;//产生了循环引用
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

这个例子在一个DOM元素(element)与一个原生js对象(myObject)之间创建了循环引用。其中,变量myObject有一个名为element的属性指向element对象;而变量element也有一个someObject属性指向myObject。由于存在这个循环引用,即使例子中的DOM从页面中移除,它也永远不会被回收。

myObject.element = null;
element.someObject = null;
  • 1
  • 2
  • 1
  • 2

为了避免类似这样的循环引用问题,最好在不使用它们的时候手工断开原生js对象与DOM元素之间的连接。将变量设置为null意味着切断变量与它此前引用的值之间的连接。当垃圾收集器下次运行时,就会删除这些值并回收它们占用的内存。
注意:为了解决上述问题,IE9把BOM和DOM对象都转换成了真正的js对象。这样,就避免了两种垃圾收集算法并存导致的问题,也消除了常见的内存泄漏现象

原生js的一些研究和总结(1)的更多相关文章

  1. 关于原生js的一些研究

    搬砖,原文地址:http://segmentfault.com/a/1190000002911253 callee和caller function inner(){ console.log(argum ...

  2. 原生JS研究:学习jquery源码,收集整理常用JS函数

    原生JS研究:学习jquery源码,收集整理常用JS函数: 1. JS获取原生class(getElementsByClass) 转自:http://blog.csdn.net/kongjiea/ar ...

  3. 再谈React.js实现原生js拖拽效果

    前几天写的那个拖拽,自己留下的疑问...这次在热心博友的提示下又修正了一些小小的bug,也加了拖拽的边缘检测部分...就再聊聊拖拽吧 一.不要直接操作dom元素 react中使用了虚拟dom的概念,目 ...

  4. React.js实现原生js拖拽效果及思考

    一.起因&思路 不知不觉,已经好几天没写博客了...近来除了研究React,还做了公司官网... 一直想写一个原生js拖拽效果,又加上近来学react学得比较嗨.所以就用react来实现这个拖 ...

  5. 【前端性能】必须要掌握的原生JS实现JQuery

    很多时候,我们经常听见有人说jquery有多快多快.在这个各种类库满天飞的时候,不得不说的是,能有原生JS快吗? 是的,明显原生JS要更快,因为诸如JQuery这样的库必须要兼容各种浏览器和低版本和许 ...

  6. 原生js动画效果(源码解析)

    在做页面中,多数情况下都会遇到页面上做动画效果,大部分都是用jquery来实现动画,今天正好看到一篇原生js实现动画效果的代码,特分享在此. 原文地址:http://www.it165.net/pro ...

  7. 原生Js 两种方法实现页面关键字高亮显示

    原生Js 两种方法实现页面关键字高亮显示 上网看了看别人写的,不是兼容问题就是代码繁琐,自己琢磨了一下用两种方法都可以实现,各有利弊. 方法一 依靠正则表达式修改 1.获取obj的html2.统一替换 ...

  8. JQuery&原生js ——实现剪刀石头布小游戏

    前言 jQuery是一个快速.简洁的JavaScript框架,是继Prototype之后又一个优秀的JavaScript代码库( 或JavaScript框架).jQuery设计的宗旨是“write L ...

  9. 原生JS插件(超详细)

    作为一个前端er,如果不会写一个小插件,都不好意思说自己是混前端界的.写还不能依赖jquery之类的工具库,否则装得不够高端.那么,如何才能装起来让自己看起来逼格更高呢?当然是利用js纯原生的写法啦. ...

随机推荐

  1. Docker + webpack 打包前端项目

    码云代码地址: https://gitee.com/caonimashi/docker_deployment_front_end    构建基础镜像: 1.下载一个 Apline Linux 操作系统 ...

  2. 【Java】 重拾Java入门

    [概论与基本语法] 取这个标题,还是感觉有些大言不惭.之前大三的时候自学过一些基本的java知识,大概到了能独立写一个GUI出来的水平把,不过后来随着有了其他目标,就把这块放下了.之后常年没有用,早就 ...

  3. 不可思议的纯CSS导航栏下划线跟随效果

    先上张图,如何使用纯 CSS 制作如下效果? 在继续阅读下文之前,你可以先缓一缓.尝试思考一下上面的效果或者动手尝试一下,不借助 JS ,能否巧妙的实现上述效果. OK,继续.这个效果是我在业务开发的 ...

  4. Spring Boot入门教程1、使用Spring Boot构建第一个Web应用程序

    一.前言 什么是Spring Boot?Spring Boot就是一个让你使用Spring构建应用时减少配置的一个框架.约定优于配置,一定程度上提高了开发效率.https://zhuanlan.zhi ...

  5. 算法题丨Longest Consecutive Sequence

    描述 Given an unsorted array of integers, find the length of the longest consecutive elements sequence ...

  6. 贯穿程序员一生的必备开发技能——debug

    1.什么是debug debug是一种运行模式,用来跟踪程序的走向,以及跟踪程序运行过程中参数的值的变化. 2.debug的作用 debug一般用来跟踪代码的运行过程,通常在程序运行结果不符合预期或者 ...

  7. 铜齿铁牙UP计划

    铜齿铁牙UP计划 我在""做教练"之好声音训练"给出了老师.播音主持学习者,声乐学习者科学用声三要点: 用气发声 共鸣发声 虚实结合 用气发声首先要学会腹式呼吸 ...

  8. C语言第四次博客作业

    一.PTA实验作业 题目1.梅森数 1.本题PTA提交列表(要提交列表,不是结果) 2. 设计思路(此处用流程图最好) 1.定义三个变量n,num,count,且初始化count为1 2.读取一个数n ...

  9. bug终结者 团队作业第四、五周

    bug终结者 团队作业第四.五周 博客编辑者:20162322朱娅霖 本周学习任务: 团队协作完成<需求规格说明书> 工作流程 第四周 团队成员各自完成蓝墨云班课中<需求规格说明书& ...

  10. Python 双向链表

    操作 is_empty() 链表是否为空 length() 链表长度 travel() 遍历链表 add(item) 链表头部添加 append(item) 链表尾部添加 insert(pos, it ...