See More

PK ;G™N meta.xml‰vþlinxqXMindR3.7.8.201807240049900600#FFFFFFPKHo㎠‰ PK ;G™N content.xml —¿h@Javascript对象六种主要类型stringnumberbooleannull

typeof(null) === object

typeof(null) === objectundefinedobject属性描述符writableenumerableconfigurablevalue遍历someeveryforEachfor ... infor ... 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();

}

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(); }
对象拷贝深拷贝JSON.parse(JSON.stringify())递归复制所有层级属性$.extend浅拷贝Object.assign()仅对最外一层做了深拷贝,里面的对象仍然是浅拷贝Object.assign()仅对最外一层做了深拷贝,里面的对象仍然是浅拷贝

'use strict';

let obj1 = { a: 0 , b: { c: 0, d:{ee: 323 }}};

let obj2 = Object.assign({}, obj1);

console.log(JSON.stringify(obj2));

// 深拷贝

obj1.a = 1;

console.log(JSON.stringify(obj1));

console.log(JSON.stringify(obj2));

// 深拷贝

obj2.a = 2;

console.log(JSON.stringify(obj1));

console.log(JSON.stringify(obj2));

// 以下两个例子标明除了最外一层,里面的对象都是浅拷贝

obj1.b.c = 3;

console.log(JSON.stringify(obj1));

console.log(JSON.stringify(obj2));

obj2.b.c = 5;

console.log(JSON.stringify(obj1));

console.log(JSON.stringify(obj2));

obj1.b.d.ee = 988;

console.log(JSON.stringify(obj1));

console.log(JSON.stringify(obj2));

obj2.b.d.ee = 123;

console.log(JSON.stringify(obj1));

console.log(JSON.stringify(obj2));

'use strict'; let obj1 = { a: 0 , b: { c: 0, d:{ee: 323 }}}; let obj2 = Object.assign({}, obj1); console.log(JSON.stringify(obj2)); // 深拷贝 obj1.a = 1; console.log(JSON.stringify(obj1)); console.log(JSON.stringify(obj2)); // 深拷贝 obj2.a = 2; console.log(JSON.stringify(obj1)); console.log(JSON.stringify(obj2)); // 以下两个例子标明除了最外一层,里面的对象都是浅拷贝 obj1.b.c = 3; console.log(JSON.stringify(obj1)); console.log(JSON.stringify(obj2)); obj2.b.c = 5; console.log(JSON.stringify(obj1)); console.log(JSON.stringify(obj2)); obj1.b.d.ee = 988; console.log(JSON.stringify(obj1)); console.log(JSON.stringify(obj2)); obj2.b.d.ee = 123; console.log(JSON.stringify(obj1)); console.log(JSON.stringify(obj2));
var其创建的全局变量无法删除(delete)无var创建的隐式全局变量可删除(delete)花括号的位置分号插入机制(semicolon insertion mechanism)原型链原型 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

});

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 });
作用域异常

function foo(a) {

var b;

console.log( a + b );

b = a;

}

foo( 2 );

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

function foo(a) { var b; console.log( a + b ); b = a; } foo( 2 ); 这是一个“未声明”的变量,因为在任何相关的作用域中都无法找到它。在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出 ReferenceError 异常
ReferenceError

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

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

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

如 var a ; a(12);

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

变量已声明,但未赋值,导致执行相应操作报错, 如 var a ; a(12); 变量a仅做了声明,但是里面被当做函数来执行
函数作用域匿名与具名立即执行函数 IIFE解决 undefined 标识符的默认值被错误覆盖导致的异常

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

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

undefined = true; // 给其他代码挖了一个大坑!绝对不要这样做!

(function IIFE( undefined ) {

var a;

if (a === undefined) {

console.log( "Undefined is safe here!" );

}

})();

将一个参数命名为 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

});

var a = 2;30 (function IIFE( def ) { def( window ); })(function def( global ) { var a = 3; console.log( a ); // 3 console.log( global.a ); // 2 });
块作用域垃圾收集

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 );

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 );
函数函数参数所有函数的参数都是按值传递的

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所指向的都是同一个内存区域,即他们存储的指针地址都一样。

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所指向的都是同一个内存区域,即他们存储的指针地址都一样。
命名函数表达式声明表达式隐式转换

栗子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);

栗子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高一点。
函数式编程柯里化多参数的函数转换成单参数的形式

function currying(fn, n) {

return function (m) {

return fn.call(this, m, n);

};

}

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

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

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

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

console.log(NaN == NaN); // false console.log(NaN === NaN); // false alert(isNaN(true)); //false(可以被转换成数值 1)
变量声明被提前(作用域范围内)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

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
闭包用处读取函数内部变量让这些变量始终保持在内存中

栗子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 不存在,未声明

}

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

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

栗子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()());

闭包就是能够读取其他函数内部变量的函数。 栗子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()());
改变环境上下文apply/callapplyfunc.apply(this, [arg1, arg2])

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

callfunc.call(this, arg1, arg2);

func.call(this, arg1, arg2);

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

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

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

绑定后立即执行绑定后非立即执行
都是为了改变函数执行上下文 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); };

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

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);

};

声明提升编译器执行流程1. 编译阶段,定义声明2. 执行阶段,保留原地函数及变量声明都会被提升

foo();

function foo() {

console.log( a ); // undefined

var a = 2;

}

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

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

var foo = function bar() {

// ...

};

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() {

// ...

};

函数声明会被提升,但是函数表达式却不会被提升。 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()... 的声明之前,但它是重复的声明(因此被忽

略了),因为函数声明会被提升到普通变量之前。

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()... 的声明之前,但它是重复的声明(因此被忽 略了),因为函数声明会被提升到普通变量之前。
被条件判断所控制的函数声明

foo(); // "b"

var a = true;

if (a) {

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

}

else {

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

}

foo(); // "b" var a = true; if (a) { function foo() { console.log("a"); } } else { function foo() { console.log("b"); } }
模块化规范CommonJS缺陷没有模块系统标准库较少没有标准接口缺乏包管理系统以同步方式加载模块,主要用在后端。 var clock = require('clock'); clock.start(); 这种写法适合服务端,因为在服务器读取模块都是在本地磁盘,加载速度很快。但是如果在客户端,加载模块的时候有可能出现“假死”状况。

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

var clock = require('clock');

clock.start();

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

AMD(依赖前置)