JavaScript原理(一)
来源:互联网 发布:amd锐龙 知乎 编辑:程序博客网 时间:2024/06/07 00:33
一、函数解析
JavaScript解析是一段一段,并非一行一行解析。同一段中function语句和函数直接量定义的函数总会被优先编译执行(该执行不是调用函数),之后才会执行其他函数。new Function()在运行时动态地被执行(导致作用域也不同于前者)。
前两者基本相同,因为被优先编译处理,new耗时非常高,每次循环都动态编译
// 三种函数创建的速度测试var zz = new Date();var st = zz.getTime();for (var i = 0; i < 10000; i ++) { var foo = function () {;} function foo() {;} // var kk = new Function()}var cc = new Date();var str = cc.getTime();console.log(str - st);
- new Function() JavaScript总是把它做为顶级作用域进行编译。
- 函数直接量,和new的形式,节省资源,避免了function语句形式占内存的弊端。
var n = 3;function cc() { var n = 5; var m = new Function('return n'); return m;}console.log(cc()()) // 3 而不是 5
二、动态调用函数
call 、apply会改变this指针。 可以把一个函数转化为方法传递给对象。但是这种是临时的,方法执行后自动销毁,对象并没有该方法。
var obj = { aa: 3 } function d () { console.log(this.aa) } // d函数临时做为对象obj的方法,this被指向obj。obj的aa是3 d.call(obj) // 3 d.apply(obj) // 3
返回数组最大值:
// 下面都能实现,其实是动态调用了Math.max()的方法。 var arr = [4, 6, 9,33] console.log(Math.max.apply(undefined, arr)) console.log(Math.max.apply(null, arr)) console.log(Math.max.apply(Object, arr))
三、函数引用,调用
遵照1、2两条,能正确识别函数是怎么调用的,则this的指向也不是问题。
1.函数调用(this指向window) add(2, 3)
2.方法调用(做为对象方法调用,this指向对象本身) obj.add()
3.构造器调用。new Foo()
// 1.创建一个空对象。var obj ={};// 2.设置这个对象的原型,就是指定__proto__的指向 obj.__proto__ = Foo.prototype;// 3.将构造函数的作用域赋给新对象(因此this就指向了新对象)Foo.call(obj);4.执行构造函数中的代码(给这个新对象添加方法和属性)5.返回这个对象(this)return obj;
4.动态调用,call、apply,会改变this指针。
四、JavaScript预编译过程
解释型语言:代码在执行时才被解释器逐行动态编译和执行,而不是在执行前就完成编译。
编译型语言:先编译后执行。
JS解析过程分两步,编译和执行。
编译:JS的预编译(预处理),把JS脚本转化为字节码。
执行:JS借助执行环节,将字节码转换为机械码,并按顺序执行。
五、非惰性求值和惰性求值
非惰性
- 非惰性:不管表达式是否被应用,只要在执行代码中都会被计算。
- 函数作为运算符号参与运算时,具有非惰性求值特性。
var a = 3; function f (arg) { return arg; } console.log(f(a,a = a * a)); // 3 console.log(f(a)); // 9
惰性
- 对函数或请求的处理延迟到真正需要结果时在进行处理。它的目的是要最小化计算机要做的工作。
// 每次调用f都会重新求值 var t; function f() { t = t ? t : new Date(); return t; } f()
改进:
var f = function () { console.log(33) // 只执行一次 var t = new Date() // 只执行一次 f = function () { return t; } return f(); } console.log(f()) console.log(f()) console.log(f()) console.log(f()) console.log(f())
六、循环性能
1、每次迭代做什么。
2、迭代次数。
var arr = [];for (var i = 0; i < arr.length; i ++) {}var j = 0;while (j < arr.length) {}var k = 0;do {} while (k < arr.length);步驟:1. 每次读取length2. 比较i 与 length的值。3. 比较操作 i < arr.length == true;是否成立4. 一次 ++ 操作5. 循环内的语句。
优化上述:
1. length是固定的,可以用变量缓存var length = arr.length
,这样length只需读取一次。
2. 倒叙循环方式,到循环的方式,将上面的2、3合成了一步(I == true,非零数字转化为true)。
for (var i = arr.length; i --) {}var j = arr.length;while(j --) {}var k = arr.length - 1;do {} while (k --);
3、 for 循环中避免声明变量。
for (var i = arr.length; i --) { var arr = []}
注意:基于函数的迭代,性能相对较差。forEach()的函数。
七、条件性能
1.条件较少时,使用if,较多时用switch。大多数情况switch性能优于if。只有当条件多时更加明显。
2.优化if的逻辑。条件的写法遵从最大概率到最小概率的写法。
if (num < 5) {} else if ( num >= 5 && num < 10) {} else {}如果你的条件出现在小于5的概率最大,那就将它放到最前面。这样就可能少去了二次或三次判断。
3.多条件成立下,才执行语句。
if (a) { if(b) { }}// 不要多层嵌套。下面这个更合理。if (a && b) {}// 同理,如果a b条件都不成立,也不要写多次嵌套判断。if (!(a && b)) {}
4.部分情况,可以采用查表法,代替if
if (1) { console.log('元/每吨')}if (2) { console.log('元/方')}// 这是用了对象, 当然也可以用数组。远远优于条件语句,并且便于理解。function foo(arg) { var obj = { '1': '元/每吨', '2': '元/方' } return obj[arg]}// 数组相对于对象,稍有局限,就是arg是数值。function checkList(arg) { var arr = ['元/每吨', '元/方'] return arr[arg]}
隐藏问题:
// 该语句正常输出3,但是本意是想判断 a == 1 是否成立,然而下面情况正常运行。没有报错,条件却不是因为 a == 1 而成立的,这种问题就非常难以查出来。if (a = 1) { console.log(3)}// 所以,可以采用变量在又,常量在左。如果下面少写了等号,将会报错。if (1 == a) { console.log(3)}
5.for in的性能相对较差。 for in每次迭代要搜索实例或原型属性。因此付出更多开销。除非要对数目不祥的属性进行操作。否则尽量便面使用for in
八、递归。
优点:
1.一个简单的阶乘递归,递归的速度非常快。
2.在进行复杂的算法时,递归也相对方便。
3.用递归实现的算法,都可以用迭代(迭代会有一定的性能损耗)。
缺点:
1.错误或缺少终止条件会导致浏览器假死。
2.受到调用栈的大小影响。如果超出提示错误。Uncaught RangeError: Maximum call stack size exceeded
function aa (n) { if (n === 0) { return 1 } else { return n * aa(n-1) }}// try catch 捕获错误try { aa(11111111111111110)} catch (ex) { console.log('dfsdfsdf')}
采用制表优化递归。
在函数内部建立一个缓存对象,预制两个简单阶乘。(递归的流程可以打断点自己研究)
var i = 0;function aaa (n) { console.log(i ++) if (!aaa.cache) { aaa.cache = { '0': 1, '1': 1 } } if (!aaa.cache.hasOwnProperty(n)) { aaa.cache[n] = n * aaa(n-1); } return aaa.cache[n]}// 因为在计算6的阶乘时,已经计算了5和4的。所以缓存中已存在,所以上述函数只执行了8次(变量i)。var dd = aaa(6)var ee = aaa(5)var ff = aaa(4)// console.log(dd, ee, ff)
九、字符
1、replace的第二个参数推荐采用function。
var str = 'JavaScript' str.replace(/(Sc)(ri)(pt)/g, function (v1, v2, v3, v4) { // argments[0] : 匹配的内容 // argments[1] : 第一个自表达式匹配的内容 // argments[2] : 第二个自表达式匹配的内容 // argments[3] : 第三个自表达式匹配的内容 console.log(v1, v2, v3, v4); })
2、正则机制。
1.编译:创建一个正则,首先要编译,所以多次调用同一个正则时,将其存储变量,可以减少不必要的编译操作。
2.设置起始位置:正则使用时,要先确定目标字符串开始搜索位置(字符串开始位置或正则的lastIndex指定的位置。)
3.配置正则字符:设置好起始位置后,正则将会一个一个扫描目标文本和正则表达式模版,当一个失败时,正则试图回溯到扫描之前的位置,然后进入其他的可能路径。
4.成功或失败:若匹配到相同的,则匹配成功。若失败则回溯到第二步,从下一个字符重新尝试。
3、在同一个正则表达式内,可以用\后加一位或多位数字实现。\1 是第一个带括号的子表达式
var str = 'liu "yong!" yong! shun ';var str2 = 'liu "yong!" shun ';var q = /"([a-z]+\!)" \1/g;console.log(q.test(str));console.log(q.test(str2));
十、数组下标
数组下标不一定是正整数,也可以是如下的特殊字符,虽然不合语法但是可以用正常使用。其实对象的访问除了打点,还有一种访问方式,就是中括号的方式,而中括号的方式访问是可以使用变量的(非常有用)。
这种存储方式是哈希表,哈希表的访问(查表)要优于遍历数组。
var arr = []; arr[false] = 3; arr[-1] = 2; console.log(arr) // [false: 3, -1: 2] console.log(arr[false]) // 3 console.log(arr[-1]) // 2 console.log(arr.length) // 0
- JavaScript原理(一)
- JavaScript动画工作原理之(一)
- Javascript原理
- javascript原理
- javascript(一)
- Javascript(一)
- javascript (一)
- javascript(一)
- JavaScript(一)
- JavaScript(一)
- JavaScript : 一
- Javascript(一)
- javascript(一)
- Javascript(一)
- JavaScript<一>
- JavaScript(一)
- 常用JavaScript(一)javascript
- JavaScript(一)javascript计时器
- Java基础6:集合框架学习总结
- Dubbo注册中心zookeeper
- UUID除去‘-’
- 软件测试方法&分类
- 以太网
- JavaScript原理(一)
- block、inline、inline-block的学习笔记
- A 计算几何你瞎暴力
- Lua学习笔记(一)
- 类加载机制
- C# 64位win7下DllImport LoadLibrary函数失败
- 新的旅程-博客园
- Android----图片缓存技术
- linux下获取当前屏幕分辨率