JavaScript | 关键知识点梳理

1 对象

1.1 六种主要类型

  • string

  • number

  • boolean

  • null

    typeof(null) === object

  • undefined

  • object

1.2 属性描述符

  • writable
  • enumerable
  • configurable
  • value

1.3 遍历

  • some

  • every

  • forEach

  • for … in

  • for … of

    for…of是es6引入的新遍历方式,使用的是迭代器,其原理相当于:

     var myObject = {
    
     			a: 2,
    
     			b: 3
    
     		};
    
     	Object.defineProperty( myObject, Symbol.iterator, {
    
     		enumerable: false,
    
     		writable: false,
    
     		configurable: true,
    
     		value: function() {
    
     			var o = this;
    
     			var idx = 0;
    
     			var ks = Object.keys( o );
    
     			return {
    
     				next: function() {
    
     					return {
    
     					value: o[ks[idx++]],
    
     					done: (idx > ks.length)
    
     					};
    
     				}
    
     			};
    
     		}
    
     	} );
    
     	// 手动遍历 myObject
    
     	var it = myObject[Symbol.iterator]();
    
     	var x = it.next();
    
     	while(!x.done){
    
     	    console.log(x.value);;
    
     		x = it.next();
    
     	}
    

1.4 对象拷贝

  • 深拷贝

    • JSON.parse(JSON.stringify())
    • 递归复制所有层级属性
    • $.extend
  • 浅拷贝

  • Object.assign()仅对最外一层做了深拷贝,里面的对象仍然是浅拷贝

2 var

  • 其创建的全局变量无法删除(delete)

  • 无var创建的隐式全局变量可删除(delete)

3 原型链

  • 原型 prototype

    function Person(){
    }
    Person.prototype = {
     name : "Nicholas",
     age : 29,
     job: "Software Engineer",
     sayName : function () {
     alert(this.name);
     }
    }; 
    // 此时constructor 属性不再指向 Person,而是指向object : 
    
    var friend = new Person();
    alert(friend instanceof Object); //true
    alert(friend instanceof Person); //true
    alert(friend.constructor == Person); //false
    alert(friend.constructor == Object); //true 
    
    // 若构造函数很重要则可写成 : 
    // ( 此时constructor 的 Enumerable 会被设置为true )
    
    Person.prototype = {
     constructor : Person, 
     name : "Nicholas",
     age : 29,
     job: "Software Engineer",
     sayName : function () {
     alert(this.name);
     }
    }; 
    
    // 默认情况下,原生的 constructor 属性是不可枚举的,因此如果你使用兼容 ECMAScript 5 的 JavaScript 引擎,可以试一试Object.defineProperty() : 
    
    //重设构造函数,只适用于 ECMAScript 5 兼容的浏览器
    Object.defineProperty(Person.prototype, "constructor", {
     enumerable: false,
     value: Person
    }); 
    

4 作用域

4.1 异常

function foo(a) {

    var b;

	console.log( a + b );

	b = a;

  }

  foo( 2 );

这是一个“未声明”的变量,因为在任何相关的作用域中都无法找到它。在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出 ReferenceError 异常

  • ReferenceError

    在作用域中找不到相应变量就会提示该错误

  • TypeError

    变量已声明,但未赋值,导致执行相应操作报错,

    如 var a ; a(12);

    变量a仅做了声明,但是里面被当做函数来执行

4.2 函数作用域

  • 匿名与具名

  • 立即执行函数 IIFE

    • 解决 undefined 标识符的默认值被错误覆盖导致的异常

      将一个参数命名为 undefined,但是在对应的位置不传入任何值,这样就

      可以保证在代码块中 undefined 标识符的值真的是 undefined:

        undefined = true; // 给其他代码挖了一个大坑!绝对不要这样做!
      
      	  (function IIFE( undefined ) {
      
      	    var a;
      
      	    if (a === undefined) {
      
      	       console.log( "Undefined is safe here!" );
      
      	     }
      
      	  })();
      
    • 倒置代码的运行顺序

        var a = 2;30 
      
      	  (function IIFE( def ) {
      
      	    def( window );
      
      	  })(function def( global ) {
      
      	     var a = 3;
      
      	     console.log( a ); // 3
      
      	     console.log( global.a ); // 2
      
      	  });
      

4.3 块作用域

  • 垃圾收集

      function process(data) {
    
       // 在这里做点有趣的事情
    
      }
    
      var someReallyBigData = { .. };
    
      process( someReallyBigData );
    
      var btn = document.getElementById( "my_button" );
    
      btn.addEventListener( "click", function click(evt) {
    
            console.log("button clicked");
    
      }, /*capturingPhase=*/false );
    
    

    click 函数的点击回调并不需要 someReallyBigData 变量。理论上这意味着当 process(…) 执

    行后,在内存中占用大量空间的数据结构就可以被垃圾回收了。但是,由于 click 函数形成

    了一个覆盖整个作用域的闭包, JavaScript 引擎极有可能依然保存着这个结构(取决于具体

    实现)。块作用域可以打消这种顾虑,可以让引擎清楚地知道没有必要继续保存 someReallyBigData 了:

    function process(data) {
    
       // 在这里做点有趣的事情
    
      }
    
      // 在这个块中定义的内容完事可以销毁!
    
      {
    
       let someReallyBigData = { .. };
    
       process( someReallyBigData );
    
      }
    
      var btn = document.getElementById( "my_button" );
    
      btn.addEventListener( "click", function click(evt){
    
       console.log("button clicked");
    
      }, /*capturingPhase=*/false );
    

5 函数

5.1 函数参数

  • 所有函数的参数都是按值传递的

    Example:

    function setName(obj) {
    
       obj.name = "Nicholas";
    
       obj = new Object();
    
       obj.name = "Greg";
    
      }
    
      var person = new Object();
    
      setName(person);
    
      alert(person.name); //"Nicholas" 
    

    参数按值传递,执行setName函数时会将person所指向的Object实例地址复制一份,传到参数内部。该副本及其person所指向的都是同一个内存区域,即他们存储的指针地址都一样。

5.2 命名函数表达式

  • 声明
  • 表达式

5.3 隐式转换

// 栗子1: 

function fn() {

    return 20;

}



console.log(fn + 10); 



// 栗子2: 

function fn() {

    return 20;

}



fn.toString = function() {

    return 10;

}



console.log(fn + 10);  // 输出结果是多少?



// 栗子3:



function fn() {

    return 20;

}



fn.toString = function() {

    return 10;

}



fn.valueOf = function() {

    return 5;

}



console.log(fn + 10);
  • 当我们没有重新定义toString与valueOf时,函数的隐式转换会调用默认的toString方法,它会将函数的定义内容作为字符串返回。而当我们主动定义了toString/vauleOf方法时,那么隐式转换的返回结果则由我们自己控制了。其中valueOf的优先级会toString高一点。

5.4 函数式编程

  • 柯里化

    • 多参数的函数转换成单参数的形式

        function currying(fn, n) {
      
      	    return function (m) {
      
      	      return fn.call(this, m, n);
      
      	    };
      
      	  }
      

6 变量

6.1 NaN

console.log(NaN == NaN);    // false
console.log(NaN === NaN);  // false

alert(isNaN(true)); //false(可以被转换成数值 1)

6.2 null 与 undefined

  1. null 不是空对象指针,而是基本数据类型,故有:

      typeof(null)  // object
    
    

    在 JavaScript 中二进制前三位都为 0 的话会被判

    断为 object 类型, null 的二进制表示是全 0,自然前三位也是 0,所以执行 typeof 时会返回“object”

  2. undefined 派生自 null

       console.log(null == undefined);  //true 
       console.log(null === undefined);  //false 
    

7 闭包

闭包就是能够读取其他函数内部变量的函数。

// 栗子1: 
// getNameFunc返回的匿名函数的执行环境是全局的,而且this只在函数内部起作用。此时的this.name在匿名函数中找不到,所以就从全局中找,找到后打印出来。

  var name = "The Window";

  var object = {

    name : "My Object",

    getNameFunc : function(){

      return function(){

        return this.name;

      };

    }

  };
  alert(object.getNameFunc()());



// 栗子2:

   var name = "The Window";

  var object = {

    name : "My Object",

    getNameFunc : function(){

      var that = this;

      return function(){

        return that.name;

      };

    }

  };

  alert(object.getNameFunc()());

7.1 用处

  • 读取函数内部变量
  • 让这些变量始终保持在内存中
  // 栗子1:

  

   function f1(){

     var n=999;

     nAdd=function(){n+=1}

 

     function f2(){

       alert(n);

     }

     return f2;

   }

 

   var result=f1();

 

   result(); // 999

 

   nAdd();

 

   result(); // 1000

  

// 原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

  

  //  栗子2: 

  

  for( var i = 0; i < 5; i++ ) {

  	setTimeout(() => {

  		console.log( i );

  	}, 1000 * i)

  }

  // 结果为5个5,每秒输出一个

  

 // 根据作用域的工作原理,实际情况是尽管循环中的五个函数是在各个迭代中分别定义的,

 // 但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个 i。

  

//  栗子3: 

  for (var i = 1; i <= 5; i++) {

      let j = i;  // 输出结果正常

      setTimeout(function timer() {

          console.log(j);

      },j*1000);

  }

  

//  栗子4:

  

  for (var i = 1; i <= 5; i++) {

      var j = i; // 输出结果仍然是5个5

      setTimeout(function timer() {

          console.log(j);

      },j*1000);

  }

  

 // 栗子5:

  for( var i = 0; i < 5; i++ ) {

  	((j) => {

  		setTimeout(() => {

  			console.log( j );

  		}, 1000 * j)

  	})(i)	

  }

  

 // 栗子6: 

  

  function test(){

       for (let i=0; i<5; i++) {  // 正常

          setTimeout( function timer() {

              console.log(new Date(),i);

          }, i*1000 );

      }

   //   console.log("end",new Date(),i);  //因为变量作用域的问题,这里会报i 不存在,未声明    

  }

7.2 注意

  • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,
    所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。
    解决方法是,在退出函数之前,将不使用的局部变量全部删除。
  • 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

8 改变环境上下文

8.1 apply/call

都是为了改变函数执行上下文

  1. 改变this指针,第一个参数都是this要指向的对象,也就是想指定的上下文;

  2. 借用其他对象的方法

下面就借用一道面试题,来更深入的去理解下 apply 和 call 。

定义一个 log 方法,让它可以代理 console.log 方法,常见的解决方法是:

function log(msg) {

  console.log(msg);

}

log(1);    //1

log(1,2);    //1

// 上面方法可以解决最基本的需求,但是当传入参数的个数是不确定的时候,上面的方法就失效了,这个时候就可以考虑使用 apply 或者 call,注意这里传入多少个参数是不确定的,所以使用apply是最好的,方法如下:



function log(){

  console.log.apply(console, arguments);

};

log(1);    //1

log(1,2);    //1 2

// 接下来的要求是给每一个 log 消息添加一个"(app)"的前辍,比如:



log("hello world");    //(app)hello world

// 该怎么做比较优雅呢?这个时候需要想到arguments参数是个伪数组,通过 Array.prototype.slice.call 转化为标准数组,再使用数组方法unshift,像这样:





function log(){

  var args = Array.prototype.slice.call(arguments);

  args.unshift('(app)');



  console.log.apply(console, args);

};
  • apply

     func.apply(this, [arg1, arg2])
    
  • call

      func.call(this, arg1, arg2);
    
  • bind

    绑定后返回一个函数,待后续调用。

    多次 bind() 是无效的,更深层次的原因, bind() 的实现,相当于使用函数在内部包了一个 call / apply ,第二次 bind() 相当于再包住第一次 bind() ,故第二次以后的 bind 是无法生效的。

9 声明提升

9.1 编译器执行流程

    1. 编译阶段,定义声明
    1. 执行阶段,保留原地

9.2 函数及变量声明都会被提升

foo();

function foo() {

   console.log( a ); // undefined

   var a = 2;

}



// 函数声明会被提升,但是函数表达式却不会被提升。

foo(); // 不是 ReferenceError, 而是 TypeError!

var foo = function bar() {

// ...

};
// 函数表达式不会被提升

// 函数声明会被提升,但是函数表达式却不会被提升。

  foo(); // 不是 ReferenceError, 而是 TypeError!

  var foo = function bar() {

  // ...

  };

  

  // 即使是具名的函数表达式,名称标识符在赋值之前也无法在所在作用域中使用:

  foo(); // TypeError

  bar(); // ReferenceError

  var foo = function bar() {

  // ...

  };
// 使用let定义的变量不会被提升

// 函数声明优先被提升

foo(); // 1



var foo;

function foo() {

   console.log( 1 );

}

foo = function() {

    console.log( 2 );

};



// 会输出 1 而不是 2 !这个代码片段会被引擎理解为如下形式:

function foo() {

   console.log( 1 );

}

foo(); // 1

foo = function() {

   console.log( 2 );

};



// 注意, var foo 尽管出现在 function foo()... 的声明之前,但它是重复的声明(因此被忽略了),因为函数声明会被提升到普通变量之前。

9.3 被条件判断所控制的函数声明

foo(); // "b"

var a = true;

if (a) {

   function foo() { console.log("a"); }

}

else {

   function foo() { console.log("b"); }

}

10 模块化规范

10.1 CommonJS

以同步方式加载模块,主要用在后端。

var clock = require('clock');

clock.start();

这种写法适合服务端,因为在服务器读取模块都是在本地磁盘,加载速度很快。但是如果在客户端,加载模块的时候有可能出现“假死”状况。

  • 缺陷

    • 没有模块系统
    • 标准库较少
    • 没有标准接口
    • 缺乏包管理系统

10.2 AMD(依赖前置)

异步的加载模块,Asynchronous Module Definition:

require(['clock'],function(clock){

  clock.start();

});

AMD虽然实现了异步加载,但是开始就把所有依赖写出来是不符合书写的逻辑顺序的,能不能像commonJS那样用的时候再require,而且还支持异步加载后再执行呢?所以才有了CMD

CMD(依赖就近)

由国内的玉伯提出,CMD (Common Module Definition), 是seajs推崇的规范,CMD则是依赖就近,用的时候再require。它写起来是这样的:

define(function(require, exports, module) {

   var clock = require('clock');

   clock.start();

});

AMD和CMD最大的区别是对依赖模块的执行时机处理不同,而不是加载的时机或者方式不同,二者皆为异步加载模块。

11 this

绑定规则

  • 默认绑定
  • 隐式绑定
  function foo() {

  console.log( this.a );

  }

  var obj = {

  a: 2,

  foo: foo

  };

  obj.foo(); // 2

  

  

  

  function foo() {

  console.log( this.a );

  }

  var obj2 = {

  a: 42,

  foo: foo

  };

  var obj1 = {

  a: 2,

  obj2: obj2

  };

  obj1.obj2.foo(); // 42

  

 // 隐式绑定丢失,退回默认板顶的栗子1:

  

  function foo() {

  console.log( this.a );

  }

  var obj = {

  a: 2,

  foo: foo

  };

  var bar = obj.foo; // 函数别名!

  var a = "oops, global"; // a 是全局对象的属性

  bar(); // "oops, global"

  

  

  // 栗子2: 

  

  function foo() {

  console.log( this.a );

  }

  function doFoo(fn) {

  // fn 其实引用的是 foo

  fn(); // <-- 调用位置!

  }

  var obj = {

  a: 2,

  foo: foo

  };

  var a = "oops, global"; // a 是全局对象的属性

  doFoo( obj.foo ); // "oops, global"

  

  

 /// 栗子3: 

  

  function foo() {

  console.log( this.a );

  }

  var obj = {

  a: 2,

  foo: foo

  };

  var a = "oops, global"; // a 是全局对象的属性

  setTimeout( obj.foo, 100 ); // "oops, global"

  • 显式绑定
  • new绑定

12 Event loop(事件循环)

12.1 同步任务

同步任务

12.2 异步任务

  • 微观任务

    • Promise
    • process.nextTick
    • MutationObserver
  • 宏观任务

    • setTimeout
    • setInterval
    • setImmediate
    • UI rendering
    • I/O
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孟华328

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值