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. 比较ilength的值。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
原创粉丝点击