构造对象 new new 运算符接受一个函数 F 及其参数: new F(arguments…).这个一过程分为:
创建类的实例.这步是把一个空的对象的 proto 属性设置为 F.prototype.
初始化实例.函数 F 被传入参数并调用.关键字 this 被设定为该实例.
返回实例.
对于实例对象来说,都是通过 new 产生的,无论是function Foo()
还是let a = { b : 1 }
。
1 2 3 4 5 6 7 8 9 function person (name, age ) { this .name = name; this .age = age; this .sayName = function ( ) { console .log (this .name ); }; } p1 = new person ("tom" , 12 );
instanceof instanceof 是一个操作符,可以判断对象 是否为某个类型的实例
1 2 3 p1 instanceof person; p1 instanceof Object ; 1 instanceof Number ;
原型与原型链
当new
一个函数的时候会创建一个对象,函数.prototype
等于被创建对象.__proto__
一切函数都是由 Function 这个函数创建的,所以Function.prototye === 被创建函数.__proto__
一切函数的原型对象都是由Object
这个函数创建的,所以Object.prototype === 一切函数.prototype.__proto__
__proto__
把对象和原型连接起来,形成原型链
this 在函数被直接调用时this
绑定到全局对象.即 window
1 2 3 4 5 console .log (this ); function fn1 ( ) { console .log (this ); } fn1 ();
内部函数 函数嵌套产生的内部函数的this
不是他的父函数,仍然是全局,window
1 2 3 4 5 6 7 8 function fn0 ( ) { function fn ( ) { console .log (this ); } fn (); } fn0 ();
setTimeOut()和 setInterval() 这两个函数执行的函数 this 也是全局
1 2 3 4 5 6 7 8 9 10 document .addEventListener ( "click" , function (e ) { console .log (this ); setTimeout (function ( ) { console .log (this ); }, 200 ); }, false );
DOM 对象绑定事件 在事件处理程序中 this 代表事件源 DOM 对象
1 2 3 4 5 6 7 8 9 10 11 12 document .addEventListener ( "click" , function (e ) { console .log (this ); var _document = this ; setTimeout (function ( ) { console .log (this ); console .log (_document); }, 200 ); }, false );
Function.prototype.bind() bind 改变 this 的指向.返回一个新函数,并使函数内部的 this 为传入的第一个参数,并不执行
1 2 var fn3 = obj1.fn .bind (obj1);fn3 ();
使用 call 和 apply 设置 this call,apply 调用一个函数,传入函数执行上下文及其参数,并立即执行
1 2 fn.call (context,param1,param2...) fn.apply (context,paramArray)
第一个参数都是希望设置的 this 对象
举例
1 2 3 4 5 6 7 8 function sum ( ) { Array .prototype .forEach .call (arguments , function (value ) { console .log (value); }); } sum (3 , 4 , 1 , 6 );
1 2 3 4 5 6 7 8 9 10 function sum ( ) { var result = 0 ; Array .prototype .forEach .call (arguments , function (value ) { console .log (value); return (result += value); }); console .log (result); } sum (3 , 4 , 1 , 6 );
arguments
在函数调用时,会自动在该函数内部生成一个名为 arguments 的隐藏对象
该对象类似于数组,可以使用[]
运算符获取函数调用时传递的实参
只有函数被调用时,arguments 对象才会创建,未调用时其值为 null
1 2 3 4 5 6 7 8 function fn5 (name, age ) { console .log (arguments ); name = "XXX" ; console .log (arguments ); arguments [1 ] = 30 ; console .log (arguments ); } fn5 ("Byron" , 20 );
研究 this 1 2 3 4 5 6 7 8 9 var obj = { foo : function ( ) { console .log (this ); }, }; var bar = obj.foo ;obj.foo (); bar ();
实际上的正常调用方式
func.call(context, p1, p2)
其他简化方式都可以转化
1 2 3 4 5 func (p1, p2) 等价于func.call (undefined , p1, p2) obj.child .method (p1, p2) 等价于 obj.child .method .call (obj.child , p1, p2)
this,就是上面代码中的 context。就这么简单。
this 是你 call 一个函数时传的 context,由于你从来不用 call 形式的函数调用,所以你一直不知道。
浏览器规则:如果你传的 context 就 null 或者 undefined,那么 window 对象就是默认的 context(严格模式下默认 context 是 undefined)
上面的代码就解释的通了
1 2 3 4 5 6 7 8 9 10 11 12 13 var obj = { foo : function ( ) { console .log (this ); }, }; var bar = obj.foo ;obj.foo (); bar ();
Event Handler 中的 this 1 2 3 btn.addEventListener ("click" , function handler ( ) { console .log (this ); });
当使用 addEventListener() 为一个元素注册事件的时候,句柄里的 this 值是该元素的引用。其与传递给句柄的 event 参数的 currentTarget 属性的值一样。
jQuery Event Handler 中的 this 那么下面代码中的 this 是什么呢:
1 2 3 $ul.on ("click" , "li" , function ( ) { console .log (this ); });
当 jQuery 的调用处理程序时,this 关键字指向的是当前正在执行事件的元素。对于直接事件而言,this 代表绑定事件的元素。对于代理事件而言,this 则代表了与 selector 相匹配的元素。(注意,如果事件是从后代元素冒泡上来的话,那么 this 就有可能不等于 event.target。)若要使用 jQuery 的相关方法,可以根据当前元素创建一个 jQuery 对象,即使用 $(this)。
[]语法 1 2 3 4 5 function fn ( ) { console .log (this ); } var arr = [fn, fn2];arr[0 ]();
我们可以把 arr0 想象为 arr.0( ),虽然后者的语法错了,但是形式与转换代码里的 obj.child.method(p1, p2) 对应上了,于是就可以愉快的转换了:
1 2 3 4 arr[0 ]() 假想为 arr.0 () 然后转换为 arr.0 .call (arr) 那么里面的 this 就是 arr 了
call、apply 、函数执行的本质 当我们执行一个函数,以下几种调用方式等价
1 2 3 4 5 6 7 8 "use strict" ;function fn (a, b ) { console .log (this ); } fn (1 , 2 );fn.call (undefined , 1 , 2 ); fn.apply (undefined , [1 , 2 ]);
在严格模式下, fn 里的 this 就是 call 的第一个参数,也就是 undefined。
在非严格模式下(不加”use strict”), call 传递的第一个参数如果是 undefined 或者 null, 那 this 会自动替换为 Window 对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var obj = { fn : function (a, b ) { console .log (this ); }, child : { fn2 : function ( ) { console .log (this ); }, }, }; obj.fn (1 , 2 ); obj.fn .call (obj, 1 , 2 ); obj.fn .apply (obj, [1 , 2 ]); obj.child .fn2 (); obj.child .fn2 .call (obj.chid );
箭头函数中的 this
箭头函数会捕获其所在上下文的 this 值作为自己的 this 值,自己本身并没有 this 值.
箭头函数的 this 永远指向其上下文的 this,任何方法都改变不了其指向,如 call,bind,apply.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 let app = { fn1 : function (a ) { console .log (this ); }, fn2 (a ) { console .log (this ); }, fn3 : (a ) => { console .log (this ); }, }; app.fn2 .call (app); app.fn3 .call (它上下文的this );
箭头函数的复杂情况示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 var app = { fn1 ( ) { setTimeout (function ( ) { console .log (this ); }, 10 ); }, fn2 ( ) { setTimeout (() => { console .log (this ); }, 20 ); }, fn3 ( ) { setTimeout ( function ( ) { console .log (this ); }.bind (this ), 30 ); }, fn4 : () => { setTimeout (() => { console .log (this ); }, 40 ); }, }; app.fn1 (); app.fn2 (); app.fn3 (); app.fn4 (); 以上代码相当于; var app = { fn1 ( ) { function fn ( ) { console .log (this ); } }, fn2 ( ) { }, fn3 ( ) { }, fn4 : () => { }, };
函数的执行环境 JavaScript 中的函数既可以被当作普通函数执行,也可以作为对象的方法执行,这是导致 this 含义如此丰富的主要原因
一个函数被执行时,会创建一个执行环境(ExecutionContext),函数的所有的行为均发生在此执行环境中,构建该执行环境时,JavaScript 首先会创建 arguments 变量,其中包含调用函数时传入的参数
接下来创建作用域链,然后初始化变量。首先初始化函数的形参表,值为 arguments 变量中对应的值,如果 arguments 变量中没有对应值,则该形参初始化为 undefined。
如果该函数中含有内部函数,则初始化这些内部函数。如果没有,继续初始化该函数内定义的局部变量,需要注意的是此时这些变量初始化为 undefined,其赋值操作在执行环境(ExecutionContext)创建成功后,函数执行时才会执行,这点对于我们理解 JavaScript 中的变量作用域非常重要,最后为 this 变量赋值,会根据函数调用方式的不同,赋给 this 全局对象,当前对象等
至此函数的执行环境(ExecutionContext)创建成功,函数开始逐行执行,所需变量均从之前构建好的执行环境(ExecutionContext)中读取.
每个执行上下文中都有三个重要的属性
变量对象(VO),包含变量、函数声明和函数的形参,该属性只能在全局上下文中访问
作用域链(JS 采用词法作用域,也就是说变量的作用域是在定义时就决定了)
this
三种变量 实例变量:(this)类的实例才能访问到的变量
静态变量:(属性)直接类型对象能访问到的变量
私有变量:(局部变量)当前作用域内有效的变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function ClassA ( ) { var a = 1 ; this .b = 2 ; } ClassA .c = 3 ; console .log (a); console .log (ClassA .b ); console .log (ClassA .c ); var classa = new ClassA ();console .log (classa.a ); console .log (classa.b ); console .log (classa.c );
继承 继承是指一个对象直接使用另一个对象的属性和方法.
如果实现以下两点就实现了继承
得到一个类的属性
得到一个类的方法
原型继承,核心在于在子类的构造函数中通过parent.call(this)
继承父亲的属性,然后改变子类的原型为new parent()
来继承父类的函数.
属性的获取 对象属性的获取是通过构造函数的执行.在一个类中执行另外一个类的构造函数,就可以把属性赋值到自己内部,
但是需要把环境改到自己的作用域内,用call
修改 this 的指向即可.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function Person (name, sex ) { this .name = name; this .sex = sex; } Person .prototype .printName = function ( ) { console .log (this .name ); }; function Male (age ) { this .age = age; } Male .prototype .printAge = function ( ) { console .log (this .age ); }; function Male (name, sex, age ) { Person .call (this , name, sex); this .age = age; } Male .prototype .printAge = function ( ) { console .log (this .age ); }; var m = new Male ("Tom" , "male" , 10 );console .log (m.sex );
继承的范例 1 2 3 4 5 6 7 8 9 10 function Male (name, sex, age ) { Person .call (this , name, sex); this .age = age; } Male .prototype = Object .create (Person .prototype );Male .prototype .printAge = function ( ) { console .log (this .age ); };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function Person (name, sex ) { this .name = name; this .age = age; } Person .prototype .printName = function ( ) { console .log (this .name ); }; function Male (name, sex, age ) { Person .call (this , name, age); this .sex = sex; } Male .prototype = new Person ();Male .prototype .constuctor = Male ;Male .prototype .printAge = function ( ) { console .log (this .age ); }; var man = new Male ("Tom" , "Male" , 10 );man.printName ();
hasOwnProperty 判断属性是自己的还是继承的
1 2 m.hasOwnProperty ("name" ); m.hasOwnProperty ("printName" );
Promise 对象 回调地狱 下列代码实现按顺序执行,1 秒后执行 fn1,再过 1 秒执行 fn2,再过 1 秒执行 fn3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function fn1 (callback ) { setTimeout (() => { console .log ("fn1" ); callback (); }, 1000 ); } function fn2 (callback ) { setTimeout (() => { console .log ("fn2" ); callback (); }, 1000 ); } function fn3 ( ) { setTimeout (() => { console .log ("fn3" ); }, 1000 ); } fn1 (function ( ) { fn2 (function ( ) { fn3 (); }); });
由于层层嵌套,形成回调地狱 .(如果套个十几二十个,真的要崩溃)
什么是 Promise Promise 对象用于表示一个异步操作的最终完成 (或失败), 及其结果值.(MDN 解释)
Promise 是一个对象,对象里存储着状态.分别是 pending(等待态),fulfilled(完成态),rejected(拒绝态)
Promise 启动之后,当满足成功的条件时我们让状态从 pending 变成 fullfilled (执行 resolve);当满足失败的条件,我们让状态从 pending 变成 rejected(执行 reject)
面试官想听的版本 :
所谓 Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理,让开发者不用再关注于时序和底层的结果。Promise 的状态具有不受外界影响和不可逆两个特点。
then 函数会返回一个 Promise 实例,并且该返回值是一个新的实例而不是之前的实例。因为 Promise 规范规定除了 pending 状态,其他状态是不可以改变的,如果返回的是一个相同实例的话,多个 then 调用就失去意义了.
then 的参数有两个,也是一个成功函数,一个失败函数.
1 const promise2 = doSomething ().then (successCallback, failureCallback);
Promise+ajax 范例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 function getIp ( ){ return new Promise (function (resolve,reject ){ var xhr = XMLHttpRequest () xhr.open ('GET' ,'https://easy-mock.com/mock/5ac2f80c3d211137b3f2843a/promise/getIp' ,true ) xhr.onload = function ( ){ var retJson = JSON .parse (xhr.responText ) resolve (retJson.ip ) } xhr.onerror = function ( ){ reject ('获取IP失败' ) } xhr.send () }) } function getCityFromIp (ip ){ return new Promise (function (resolve, reject ){ var xhr = XMLHttpRequest () xhr.open ('GET' , 'https://easy-mock.com/mock/5ac2f80c3d211137b3f2843a/promise/getCityFromIp?ip=' +ip, true ) xhr.onload = function ( ){ var retJson = JSON .parse (xhr.responText ) resolve (retJson.city ) } xhr.onerror = function ( ){ reject ('获取city失败' ) } xhr.send () }) } function getWeatherFromCity (city{ return new Promise (function (resolve, reject){ var xhr = XMLHttpRequest() xhr.open('GET' , 'https://easy-mock.com/mock/5ac2f80c3d211137b3f2843a/promise/getWeatherFromCity?city=' +city, true ) xhr.onload = function (){ var retJson = JSON .parse(xhr.responText) resolve(retJson) } xhr.onerror = function (){ reject('获取天气失败' ) } xhr.send() }) } )getIp ().then (function (ip ){ return getCityFromIp (ip) }).then (function (city ){ return getWeatherFromCity (city) }).then (function (data ){ console .log (data) }).catch (function (e ){ console .log ('出现了错误' ,e) })
Promise.all 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function getCityFromIp (ip ){ return new Promise (function (resolve, reject ){ var xhr = XMLHttpRequest () xhr.open ('GET' ,'https://easy-mock.com/mock/5ac2f80c3d211137b3f2843a/promise/getCityFromIp?ip=' +ip',true) xhr.onload = function(){ var retJson = JSON.parse(xhr.responText) resolve(retJson) } xhr.onerror = function(){ reject(' 获取city失败') } xhr.send() }) } var p1 = getCityFromIp(' 10.10 .10 .1 ') var p2 = getCityFromIp(' 10.10 .10 .2 ') var p3 = getCityFromIp(' 10.10 .10 .3 ') //Promise.all, 当所有的 Promise 对象都完成后再执行 Promise.all([p1,p2,p3]).then(data => { console.log(data) })
Promise.race 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 function getCityFromIp (ip ) { var promise = new Promise (function (resolve, reject ) { var xhr = new XMLHttpRequest (); xhr.open ( "GET" , "https://easy-mock.com/mock/5ac2f80c3d211137b3f2843a/promise/getCityFromIp?ip=" + ip, true ); xhr.onload = function ( ) { var retJson = JSON .parse (xhr.responseText ); resolve (retJson); }; xhr.onerror = function ( ) { reject ("获取city失败" ); }; setTimeout (() => { xhr.send (); }, Math .random () * 1000 ); }); return promise; } var p1 = getCityFromIp ("10.10.10.1" );var p2 = getCityFromIp ("10.10.10.2" );var p3 = getCityFromIp ("10.10.10.3" );Promise .race ([p1, p2, p3]).then ((data ) => { console .log (data); });
callback&Promise&async/await 把一个需求不断简化
需求如下:
读取 a.md 文件,得到内容
把内容转换成 HTML 字符串
把 HTML 字符串写入 b.html
callback()处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 var fs = require ("fs" );var markdown = require ("markdown" ).markdown ;fs.readFile ("a.md" , "UTF-8" , function (err, str ) { if (err) { return console .log (err); } var html = markown.toHTML (str); fs.writeFile ("b.html" , html, function (err ) { if (err) { return console .log (err); } console .log ("write.success" ); }); });
ES6 语法简化处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 let fs = require ("fs" );let markdown = require ("markdown" ).markdown ;fs.readFile ("a.md" , "UTF-8" , (err, str ) => { if (err) { return console .log (err); } let html = markdown.toHTML (str); fs.writeFile ("b.html" , html, (err ) => { if (err) { return console .log (err); } console .log ("write.success" ); }); });
Promise 处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 const fs = require ("fs" );const markdown = require ("markdown" ).markdown ;readFile ("a.md" ) .then ((mdStr ) => { return markdown.toHTML (mdStr); }) .then ((html ) => { writeFile ("b.html" , html); }) .catch ((e ) => { console .log (e); }); function readFile (url ) { return new Promise ((resolve, reject ) => { fs.readFlie (url, "UTF-8" , (err, str ) => { if (err) { reject (new Error ("readFlie error" )); } else { resolve (str); } }); }); } function writeFile (url, data ) { return new Promise ((resolve, reject ) => { fs.writeFile (url, data, (err, str ) => { if (err) { reject (new Error ("write error" )); } else { resolve (); } }); }); }
使用模块改装上面代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const markdown = require ("markdown" ).markdown ;const fsp = require ("fs-promise" ); let onerror = (err ) => { console .error ("something wrong" ); }; fsp .readFile ("a.md" , "UTF-8" ) .then ((mdStr ) => { return markdown.toHTML (mdStr); }) .then ((html ) => { fsp.writeFile ("b.html" , html); }) .catch (onerror);
async/await 处理 1 2 3 4 5 6 7 8 9 10 11 12 13 const markdown = require ("markdown" ).markdown ;const fsp = require ("fs-promise" );let onerror = (err ) => { console .error ("sonething wrong" ); }; async function start ( ) { let mdStr = await fsp.readFile ("a.md" , "UTF-8" ); let html = markdown.toHTML (mdStr); await fsp.writeFile ("b.html" , html); } start ().catch (onerror);
方法 Promise.prototype.then(onFulfilled, onRejected) 接收成功或失败的结果回调到当前 promise, 返回一个新的 promise, 将以回调的返回值来 resolve.
Promise.prototype.catch(onRejected) 接受一个失败的结果回调到当前 promise, 返回一个新的 promise。当这个回调函数被调用,新 promise 将以它的返回值来 resolve,否则如果当前 promise 进入 fulfilled 状态,则以当前 promise 的完成结果作为新 promise 的完成结果.
Promise.prototype.finally(onFinally) 添加一个事件处理回调于当前 promise 对象,并且在原 promise 对象解析完毕后,返回一个新的 promise 对象。回调会在当前 promise 运行完毕后被调用,无论当前 promise 的状态是完成(fulfilled)还是失败(rejected)
async/await async 函数的创建时通过在函数声明语句之前加上 async 关键字,
这是异步函数的特征之一 ––它将任何函数转换为 promise。
示例如下:
1 2 3 4 5 6 7 const asyncFunction = async ( ) => { }; async function asyncFunction ( ) { }
await async 异步函数可通过await
来暂停,该关键字只能用在 async 函数内部。
每当函数执行完毕,await
返回的是任何 async 函数会返回的东西。
阮一峰老师的解释: async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句
只能放在 async 函数内部使用,不能放在普通函数里面,否则会报错。
后面放 Promise 对象,在Pending
状态时,相应的协程会交出控制权,进入等待状态。等等党永不为奴!这个是本质。
await
是 async wait 的意思,wait 的是resolve(data)
消息,并把数据data
返回。
await
后面也可以跟同步代码,不过系统会自动转化成一个 Promise 对象。
await
只关心异步过程成功的消息resolve(data)
,拿到相应的数据data
。至于失败消息reject(error)
,不关心,不处理。
await 的特点 1.建立在 promise 之上。所以,不能把它和回调函数搭配使用。但它会声明一个异步函数,并隐式地返回一个 Promise。因此可以直接 return 变量,无需使用 Promise.resolve
进行转换。
2.和 promise 一样,是非阻塞的。但不用写 then 及其回调函数,这减少代码行数,也避免了代码嵌套。而且,所有异步调用,可以写在同一个代码块中,无需定义多余的中间变量。
3.它的最大价值在于,可以使异步代码,在形式上,更接近于同步代码。
4.它总是与 await 一起使用的。并且,await 只能在 async 函数体内。
5.await 是个运算符,用于组成表达式,它会阻塞后面的代码。如果等到的是 Promise 对象,则得到其 resolve 值。否则,会得到一个表达式的运算结果。
MDN 例子:
1 2 3 4 5 6 async function hello ( ) { return (greeting = await Promise .resolve ("Hello" )); } hello ().then (alert);
简单案例:
1 2 3 4 5 6 7 8 9 10 11 12 function timeout (ms ) { return new Promise ((resolve ) => { setTimeout (resolve, ms); }); } async function asyncPrint (value, ms ) { await timeout (ms); console .log (value); } asyncPrint ("hello world" , 50 );