JS基础 引入方式 JavaScript有3种引入方式:行内式、嵌入式和外链式;
行内式:将JavaScript代码作为Html标签的属性值使用。示例如下:
1 <a href ="javascript:alert('hello')" > say hello</a >
嵌入式:或称内嵌式,使用script
标签包裹JavaScript代码,直接编写到html文件中,通常放到<head>
和<body>
或标签中。<script>
中type用于告知浏览器脚本类型,html5中默认属性值为”text/javascript”,因此,使用html5时可以省略type属性。示例如下:
1 2 3 <script > Javascript代码 </script >
外链式:也称外部式,将JavaScript代码写在一个单独的文件中,一般使用”js”作为文件的扩展名,在html
页面中使用script
标签的src属性引入”js”文件。外链适合代码量较多的情况。示例如下:
1 <script src ="./test.js" > </script >
基本语法 JavaScript是一种脚本语言,脚本通常以文本保存,只有在被调用的时候进行解释或编译。
JavaScript中必须严格区分大小写,例如Test和test是不同的;
JavaScript语句中每一行代码都要以英文的分号;
结尾,如果不写分号,浏览器会自动添加,但会消耗一些系统资源;
JavaScript中会自动忽略多个空格和换行,所以可以使用空格和换行进行格式化;
JavaScript是弱类型语言,声明变量时可以不需要指定变量的类型。
面向对象/设计模式/BOM/DOM 面向对象 创建对象方式 面向对象和面向过程都是编程思想。面向对象其实是面向过程的一种封装,例如:我要吃面条(面向过程:用多少面粉、多少水、怎么和面、怎么切面条、烧开水、煮面、吃面;面向对象:找到一个面馆、叫一碗面、等着吃)。
在ES6之前,JavaScript没有类的概念,但是可以使用函数来进行模拟,从而产生可复用的对象创建方式。
创建对象的方式 :工厂模式、构造函数模式、原型模式、组合使用构造函数模式和原型模式、动态原型模式、寄生构造函数模式;还有一些通过:{}、new Object()、使用字面量这三种都属于创建对象,只是这三种的都存在2个问题,一是代码冗余,二是对象中的方法不能共享,每个方法都是独立的。
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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 function createObject (name ){ var o = new Object (); o.name = name; return o; } var o1 = createObject ("test1" );var o2 = createObject ("test2" );console .log (o1,o2);function createObject (name ){ this .name = name; } var o1 = new createObject ("test11" );var o2 = new createObject ("test22" );console .log (o1,o2);console .log (o1.prototype .constructor == createObject); function createObject ( ) {}createObject.prototype .name = "test" ; var o1 = new createObject ();console .log (o1);console .log (createObject.prototype .constructor == createObject); function createObject (name ) { this .name = name; } createObject.prototype = { constructor : createObject, sayName : function ( ){ console .log (this .name ) } } var o1 = new createObject ("test1" );var o2 = new createObject ("test2" );console .log (o1);console .log (o2);function createObject (name ){ this .name = name; if (typeof this .sayName != "function" ){ createObject.prototype = { constructor : createObject, sayName : function ( ){ console .log (this .name ); } } return new createObject (name); } } var o1 = new createObject ("test1" );var o2 = new createObject ("test2" );console .log (o1, o2);o1.sayName (); o2.sayName (); function createObject (name ){ var o = new Object (); o.name = name; return o; } function SpecialArray ( ) { var arr = new Array (); arr.push .apply (arr, arguments ); arr.toPipedString = function ( ) { return this .join ("|" ); } return arr; } var colors = new SpecialArray ('red' , 'blue' , 'green' );console .log (colors.toPipedString ());
对象的继承方式 对象的继承方式主要有:原型链继承、借用构造函数方式、组合继承、原型式继承、寄生式继承、寄生式组合继承。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
设计模式 设计模式分类为创建型(单例模式、原型模式、工厂模式、抽象工厂模式、建造者模式)、结构型(适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式)、行为型(观察者模式、迭代器模式、策略模式、模板方法模式、职责链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、发布-订阅模式)。
创建型 (单例模式、原型模式、工厂模式、抽象工厂模式、建造者模式)
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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 class LoginForm { constructor ( ) { this .state = 'hide' } show ( ) { if (this .state === 'show' ) { alert ('已经显示' ) return } this .state = 'show' console .log ('登录框显示成功' ) } hide ( ) { if (this .state === 'hide' ) { alert ('已经隐藏' ) return } this .state = 'hide' console .log ('登录框隐藏成功' ) } } LoginForm .getInstance = (function ( ) { let instance return function ( ) { if (!instance) { instance = new LoginForm () } return instance } })() let obj1 = LoginForm .getInstance ()obj1.show () let obj2 = LoginForm .getInstance ()obj2.hide () console .log (obj1 === obj2)class Person { constructor (name ) { this .name = name } getName ( ) { return this .name } } class Student extends Person { constructor (name ) { super (name) } sayHello ( ) { console .log (`Hello, My name is ${this .name} ` ) } } let student = new Student ("xiaoming" )student.sayHello () class Product { constructor (name ) { this .name = name } init ( ) { console .log ('init' ) } fun ( ) { console .log ('fun' ) } } class Factory { create (name ) { return new Product (name) } } let factory = new Factory ()let p = factory.create ('p1' )p.init () p.fun ()
结构型 (适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式)
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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 ajax ({ url : '/getData' , type : 'Post' , dataType : 'json' , data : { test : 111 } }).done (function ( ) {}) var $ = { ajax : function (options ) { return ajax (options) } } <ul id="ul" > <li > 1</li > <li > 2</li > <li > 3</li > </ul> <script > let ul = document .querySelector ('#ul' ); ul.addEventListener ('click' , event => { console .log (event.target ); }); </script > class Cellphone { create ( ) { console .log ('生成一个手机' ) } } class Decorator { constructor (cellphone ) { this .cellphone = cellphone } create ( ) { this .cellphone .create () this .createShell (cellphone) } createShell ( ) { console .log ('生成手机壳' ) } } let cellphone = new Cellphone ()cellphone.create () console .log ('------------' )let dec = new Decorator (cellphone)dec.create () let addMyEvent = function (el, ev, fn ) { if (el.addEventListener ) { el.addEventListener (ev, fn, false ) } else if (el.attachEvent ) { el.attachEvent ('on' + ev, fn) } else { el['on' + ev] = fn } }; let myEvent = { stop : e => { e.stopPropagation (); e.preventDefault (); } }; class Color { constructor (name ){ this .name = name } } class Shape { constructor (name,color ){ this .name = name this .color = color } draw ( ){ console .log (`${this .color.name} ${this .name} ` ) } } let red = new Color ('red' )let yellow = new Color ('yellow' )let circle = new Shape ('circle' , red)circle.draw () let triangle = new Shape ('triangle' , yellow)triangle.draw () class TrainOrder { create () { console .log ('创建火车票订单' ) } } class HotelOrder { create () { console .log ('创建酒店订单' ) } } class TotalOrder { constructor () { this .orderList = [] } addOrder (order) { this .orderList .push (order) return this } create () { this .orderList .forEach (item => { item.create () }) return this } } let train = new TrainOrder ()let hotel = new HotelOrder ()let total = new TotalOrder ()total.addOrder (train).addOrder (hotel).create () let examCarNum = 0 class ExamCar { constructor (carType ) { examCarNum++ this .carId = examCarNum this .carType = carType ? '手动档' : '自动档' this .usingState = false } examine (candidateId ) { return new Promise ((resolve => { this .usingState = true console .log (`考生- ${ candidateId } 开始在${ this .carType } 驾考车- ${ this .carId } 上考试` ) setTimeout (() => { this .usingState = false console .log (`%c考生- ${ candidateId } 在${ this .carType } 驾考车- ${ this .carId } 上考试完毕` , 'color:#f40' ) resolve () }, Math .random () * 2000 ) })) } } ManualExamCarPool = { _pool : [], _candidateQueue : [], registCandidates (candidateList ) { candidateList.forEach (candidateId => this .registCandidate (candidateId)) }, registCandidate (candidateId ) { const examCar = this .getManualExamCar () if (examCar) { examCar.examine (candidateId) .then (() => { const nextCandidateId = this ._candidateQueue .length && this ._candidateQueue .shift () nextCandidateId && this .registCandidate (nextCandidateId) }) } else this ._candidateQueue .push (candidateId) }, initManualExamCar (manualExamCarNum ) { for (let i = 1 ; i <= manualExamCarNum; i++) { this ._pool .push (new ExamCar (true )) } }, getManualExamCar ( ) { return this ._pool .find (car => !car.usingState ) } } ManualExamCarPool .initManualExamCar (3 ); ManualExamCarPool .registCandidates ([1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ]);
行为型 (观察者模式、迭代器模式、策略模式、模板方法模式、职责链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、发布-订阅模式)
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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 class Subject { constructor ( ) { this .state = 0 this .observers = [] } getState ( ) { return this .state } setState (state ) { this .state = state this .notifyAllObservers () } notifyAllObservers ( ) { this .observers .forEach (observer => { observer.update () }) } attach (observer ) { this .observers .push (observer) } } class Observer { constructor (name, subject ) { this .name = name this .subject = subject this .subject .attach (this ) } update ( ) { console .log (`${this .name} update, state: ${this .subject.getState()} ` ) } } let s = new Subject ()let o1 = new Observer ('o1' , s)let o2 = new Observer ('02' , s)s.setState (12 ) class Iterator { constructor (conatiner ) { this .list = conatiner.list this .index = 0 } next ( ) { if (this .hasNext ()) { return this .list [this .index ++] } return null } hasNext ( ) { if (this .index >= this .list .length ) { return false } return true } } class Container { constructor (list ) { this .list = list } getIterator ( ) { return new Iterator (this ) } } let container = new Container ([1 , 2 , 3 , 4 , 5 ])let iterator = container.getIterator ()while (iterator.hasNext ()) { console .log (iterator.next ()) } <html> <head> <title>策略模式-校验表单</title> <meta content="text/html; charset=utf-8" http-equiv="Content-Type"> </head> <body> <form id = "registerForm" method="post" action="http://xxxx.com/api/register"> 用户名:<input type="text" name="userName"> 密码:<input type="text" name="password"> 手机号码:<input type="text" name="phoneNumber"> <button type="submit">提交</button> </form> <script type="text/javascript"> // 策略对象 const strategies = { isNoEmpty: function (value, errorMsg) { if (value === '') { return errorMsg; } }, isNoSpace: function (value, errorMsg) { if (value.trim() === '') { return errorMsg; } }, minLength: function (value, length, errorMsg) { if (value.trim().length < length) { return errorMsg; } }, maxLength: function (value, length, errorMsg) { if (value.length > length) { return errorMsg; } }, isMobile: function (value, errorMsg) { if (!/^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|17[7]|18[0|1|2|3|5|6|7|8|9])\d{8}$/.test(value)) { return errorMsg; } } } // 验证类 class Validator { constructor() { this.cache = [] } add(dom, rules) { for(let i = 0, rule; rule = rules[i++];) { let strategyAry = rule.strategy.split(':') let errorMsg = rule.errorMsg this.cache.push(() => { let strategy = strategyAry.shift() strategyAry.unshift(dom.value) strategyAry.push(errorMsg) return strategies[strategy].apply(dom, strategyAry) }) } } start() { for(let i = 0, validatorFunc; validatorFunc = this.cache[i++];) { let errorMsg = validatorFunc() if (errorMsg) { return errorMsg } } } } // 调用代码 let registerForm = document.getElementById('registerForm') let validataFunc = function() { let validator = new Validator() validator.add(registerForm.userName, [{ strategy: 'isNoEmpty', errorMsg: '用户名不可为空' }, { strategy: 'isNoSpace', errorMsg: '不允许以空白字符命名' }, { strategy: 'minLength:2', errorMsg: '用户名长度不能小于2位' }]) validator.add(registerForm.password, [ { strategy: 'minLength:6', errorMsg: '密码长度不能小于6位' }]) validator.add(registerForm.phoneNumber, [{ strategy: 'isMobile', errorMsg: '请输入正确的手机号码格式' }]) return validator.start() } registerForm.onsubmit = function() { let errorMsg = validataFunc() if (errorMsg) { alert(errorMsg) return false } } </script> </body> </html> //模板方法模式:模板方法模式由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法和封装子类中所有方法的执行顺序。子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。 /* 优点:提取了公共代码部分,易于维护; 缺点:增加了系统复杂度,主要是增加了的抽象类和类间联系; 场景:一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现;子类中公共的行为应被提取出来并集中到一个公共父类中的避免代码重复。 */ class Beverage { constructor({brewDrink, addCondiment}) { this.brewDrink = brewDrink this.addCondiment = addCondiment } /* 烧开水,共用方法 */ boilWater() { console.log('水已经煮沸=== 共用') } /* 倒杯子里,共用方法 */ pourCup() { console.log('倒进杯子里===共用') } /* 模板方法 */ init() { this.boilWater() this.brewDrink() this.pourCup() this.addCondiment() } } /* 咖啡 */ const coffee = new Beverage({ /* 冲泡咖啡,覆盖抽象方法 */ brewDrink: function() { console.log('冲泡咖啡') }, /* 加调味品,覆盖抽象方法 */ addCondiment: function() { console.log('加点奶和糖') } }) coffee.init() //职责链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止 /* 优点:降低耦合度。它将请求的发送者和接收者解耦;简化了对象。使得对象不需要知道链的结构;增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任;增加新的请求处理类很方便。 缺点:不能保证某个请求一定会被链中的节点处理,这种情况可以在链尾增加一个保底的接受者节点来处理这种即将离开链尾的请求;使程序中多了很多节点对象,可能再一次请求的过程中,大部分的节点并没有起到实质性的作用。他们的作用仅仅是让请求传递下去,从性能当面考虑,要避免过长的职责链到来的性能损耗。 场景:JS 中的事件冒泡、作用域链、原型链 */ // 请假审批,需要组长审批、经理审批、总监审批 class Action { constructor(name) { this.name = name this.nextAction = null } setNextAction(action) { this.nextAction = action } handle() { console.log( `${this.name} 审批`) if (this.nextAction != null) { this.nextAction.handle() } } } let a1 = new Action("组长") let a2 = new Action("经理") let a3 = new Action("总监") a1.setNextAction(a2) a2.setNextAction(a3) a1.handle() //命令模式:将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。 /* 优点:对命令进行封装,使命令易于扩展和修改;命令发出者和接受者解耦,使发出者不需要知道命令的具体执行过程即可执行。 缺点:使用命令模式可能会导致某些系统有过多的具体命令类。 */ // 接收者类 class Receiver { execute() { console.log('接收者执行请求') } } // 命令者 class Command { constructor(receiver) { this.receiver = receiver } execute () { console.log('命令'); this.receiver.execute() } } // 触发者 class Invoker { constructor(command) { this.command = command } invoke() { console.log('开始') this.command.execute() } } // 仓库 const warehouse = new Receiver(); // 订单 const order = new Command(warehouse); // 客户 const client = new Invoker(order); client.invoke() //备忘录模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到保存的状态。 /* 优点:给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。 缺点:消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。 场景:分页控件、撤销组件 */ //备忘类 class Memento{ constructor(content){ this.content = content } getContent(){ return this.content } } // 备忘列表 class CareTaker { constructor(){ this.list = [] } add(memento){ this.list.push(memento) } get(index){ return this.list[index] } } // 编辑器 class Editor { constructor(){ this.content = null } setContent(content){ this.content = content } getContent(){ return this.content } saveContentToMemento(){ return new Memento(this.content) } getContentFromMemento(memento){ this.content = memento.getContent() } } //测试代码 let editor = new Editor() let careTaker = new CareTaker() editor.setContent('111') editor.setContent('222') careTaker.add(editor.saveContentToMemento()) editor.setContent('333') careTaker.add(editor.saveContentToMemento()) editor.setContent('444') console.log(editor.getContent()) //444 editor.getContentFromMemento(careTaker.get(1)) console.log(editor.getContent()) //333 editor.getContentFromMemento(careTaker.get(0)) console.log(editor.getContent()) //222 //状态模式:允许一个对象在其内部状态改变的时候改变它的行为,对象看起来似乎修改了它的类 /* 优点:定义了状态与行为之间的关系,封装在一个类里,更直观清晰,增改方便;状态与状态间,行为与行为间彼此独立互不干扰;用对象代替字符串来记录当前状态,使得状态的切换更加一目了然。 缺点:会在系统中定义许多状态类;逻辑分散。 场景:一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为、一个操作中含有大量的分支语句,而且这些分支语句依赖于该对象的状态。 */ // 状态 (弱光、强光、关灯) class State { constructor(state) { this.state = state } handle(context) { console.log(`this is ${this.state} light`) context.setState(this) } } class Context { constructor() { this.state = null } getState() { return this.state } setState(state) { this.state = state } } // test let context = new Context() let weak = new State('weak') let strong = new State('strong') let off = new State('off') // 弱光 weak.handle(context) console.log(context.getState()) // 强光 strong.handle(context) console.log(context.getState()) // 关闭 off.handle(context) console.log(context.getState()) //访问者模式:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。 /* 优点:符合单一职责原则;优秀的扩展性;灵活性。 缺点:具体元素对访问者公布细节,违反了迪米特原则;违反了依赖倒置原则,依赖了具体类,没有依赖抽象;具体元素变更比较困难。 场景:对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作、需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。 */ // 访问者 class Visitor { constructor() {} visitConcreteElement(ConcreteElement) { ConcreteElement.operation() } } // 元素类 class ConcreteElement{ constructor() { } operation() { console.log("ConcreteElement.operation invoked"); } accept(visitor) { visitor.visitConcreteElement(this) } } // client let visitor = new Visitor() let element = new ConcreteElement() element.accept(visitor) //中介者模式:解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。中介者使各对象之间耦合松散,而且可以独立地改变它们之间的交互。中介者模式使网状的多对多关系变成了相对简单的一对多关系(类似于观察者模式,但是单向的,由中介者统一管理。) /* 优点:使各对象之间耦合松散,而且可以独立地改变它们之间的交互;中介者和对象一对多的关系取代了对象之间的网状多对多的关系;如果对象之间的复杂耦合度导致维护很困难,而且耦合度随项目变化增速很快,就需要中介者重构代码。 缺点:系统中会新增一个中介者对象,因 为对象之间交互的复杂性,转移成了中介者对象的复杂性,使得中介者对象经常是巨大的。中介 者对象自身往往就是一个难以维护的对象。 场景:系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象、想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。 */ class A { constructor() { this.number = 0 } setNumber(num, m) { this.number = num if (m) { m.setB() } } } class B { constructor() { this.number = 0 } setNumber(num, m) { this.number = num if (m) { m.setA() } } } class Mediator { constructor(a, b) { this.a = a this.b = b } setA() { let number = this.b.number this.a.setNumber(number * 10) } setB() { let number = this.a.number this.b.setNumber(number / 10) } } let a = new A() let b = new B() let m = new Mediator(a, b) a.setNumber(10, m) console.log(a.number, b.number) b.setNumber(10, m) console.log(a.number, b.number)
BOM BOM指的是浏览器对象模型,是指把浏览器当做一个对象来对待,这个对象主要定义了与浏览器交互的方法与接口。
BOM的核心就是window,而window对象具有双重角色,它既是通过js访问浏览器窗口的一个接口,又是一个Global(全局)对象。这意味着在网页中定义的任何对象、变量和函数,都作为全局对象的一个属性或方法存在。window对象包含localtion对象、navigator对象、screen对象等子对象,并且DOM最根本的对象document对象也是BOM的window对象的子对象。
DOM DOM指的是文档对象模型,是指把文档当做一个对象,这个对象主要定义了处理网页内容的方法和接口。
数据类型 数据类型种类(8种) JavaScript的数据类型,共有8中数据类型:undefined、null、Boolean、Number、String、Object、Symbol、BigInt。
其中Symbol和BigInt是ES6新增的数据类型:
Symbol 代表创建后独一无二且不可变的数据类型,主要为了解决可能出现的全局变量冲突的问题;
BigInt 是一种数字类型,可以表示任意精度格式的整数,使用BigInt可以安全地存储和操作大数据,即使这个数已经超出了Number能够表示的安全整数范围。
数据可以分为:原始数据类型和引用数据类型。
栈(stack)(原始数据类型):undefined、null、Boolean、Number、String、Symbol、BigInt
;
原始数据类型直接存储在栈中的简单数据段,占据空间小、大小固定,属于被频繁使用的数据,所以放入栈中存储。在数据结构中,栈中的数据存储方式是先进后出。
堆(heap)(引用数据类型):对象Object、数组Array、函数
;
引用数据类型存储在堆中的对象,占据空间大,大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。在数据结构中,堆是一个优先队列,是按照优先级来进行排序的,优先级可以按照大小来规定。
(操作系统内存被分为栈区和堆区:栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似数据结构中的栈;堆区内存一般由开发者分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。)
null和undefined的区别 :两者都属于基本数据类型,分别只有一个值。
undefined代表未定义,null代表的是空对象。
typeof null会返回Object。undefined的值是(-2)30(一个超出整数范围的数字);null的机器码是NULL指针(null指针的值全是0,也就是null的类型标签为000,和Object的类型标签一样,所以会被判定为Object)。
Boolean(布尔类型的假值):undefined、null、+0、-0、false、””、NaN。
数据类型检测方法(4种)
1 2 3 4 5 6 7 8 9 console .log (typeof 2 ); console .log (typeof true ); console .log (typeof 'str' ); console .log (typeof []); console .log (typeof function ( ){}); console .log (typeof {}); console .log (typeof undefined ); console .log (typeof null ); console .log (typeof NaN );
可以正确判断对象类型,内部运行机制是判断在其原型链中是否能找到该类型的原型。
instanceof
只能正确判断引用数据类型,而不能判断基本数据类型。instanceof
运算符可以用来测试一个对象在其原型链中是否存在一个构造函数的prototype
属性。
1 2 3 4 5 6 7 console .log (2 instanceof Number ); console .log (true instanceof Boolean ); console .log ('str' instanceof String ); console .log ([] instanceof Array ); console .log (function ( ){} instanceof Function ); console .log ({} instanceof Object );
实现原理:instanceof用于判断构造函数的prototype属性是否出现在对象的原型链中的任何位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function myInstanceof (left, right ) { let proto = Object .getPrototypeOf (left); let prototype = right.prototype ; while (true ){ if (!proto) return false ; if (proto === prototype) return true ; proto = Object .getPrototypeOf (proto); } }
constructor
有2个作用:一个是判断数据的类型,二是对象示例通过constructor
对象访问它的构造函数。需要注意,如果创建一个对象来改变它的原型,constructor
就不能用来判断数据类型了。
1 2 3 4 5 6 7 8 9 10 11 12 console .log ((2 ).constructor === Number ); console .log ((true ).constructor === Boolean ); console .log (('str' ).constructor === String ); console .log (([]).constructor === Array ); console .log ((function ( ) {}).constructor === Function ); console .log (({}).constructor === Object ); function Fn ( ){};Fn .prototype = new Array ();var f = new Fn ();console .log (f.constructor ===Fn ); console .log (f.constructor ===Array );
Object.prototype.toString.call()
Object.prototype.toString.call()
是通过Object对象原型方法toString来判断数据类型。
obj.toString()
的结果和Object.prototype.toString.call(obj)
的结果不一样:toString是Object的原型方法,而Array、Function等类型作为Object的实例,都重写了toString方法。不同的对象类型调用toString方法时,根据原型链的知识,调用的是对应的重写之后的toString方法(function类型返回内容为函数体的字符串,Array类型返回元素组成的字符串…),而不会去调用Object原型上的toString方法(返回对象的具体类型),所以采用obj.toString()不能得到其对象类型,只能将obj转换为字符串类型;因此,在想要得到对象具体的类型应调用Object原型上的toString方法。
1 2 3 4 5 6 7 8 9 10 var a = Object .prototype .toString ;console .log (a.call (2 )); console .log (a.call (true )); console .log (a.call ('str' )); console .log (a.call ([])); console .log (a.call (function ( ){})); console .log (a.call ({})); console .log (a.call (undefined )); console .log (a.call (null ));
判断数组方式(5种) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Object .prototype .toString .call (obj) == "[object Array]" ; Object .prototype .toString .call (obj).slice (8 , -1 ) == "Array" ; obj instanceof Array ; obj.__proto__ === Array .prototype ; obj.constructor === Array ; Array .isArray (obj); Array .prototype .isPrototypeOf (obj);
数组的遍历方法(10种) 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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 [1 ,2 ,3 ].forEach (function (currentValue, index, arr ){ if (currentValue == 2 ){ return } console .log (currentValue, index, arr, this ) }, {a : 123 }) var doubles = [1 ,2 ,3 ].map (function (currentValue, index, arr ){ console .log (currentValue, index, arr, this ) return currentValue * 2 }, {a : 123 }); console .log (doubles)var doubles = [1 ,2 ,3 ].filter (function (currentValue, index, arr ){ console .log (currentValue, index, arr, this ) return currentValue >= 2 ; }, {a : 123 }); console .log (doubles)let iterable = [30 , 20 , 10 ];for (let value of iterable) { value += 1 ; console .log (value); } var arr = [1 ,2 ,3 ]for (let index in arr) { let res = index + 1 console .log (res) } var arr = {a :1 , b :2 }for (let index in arr) { console .log (index) } const numberFlag = [1 ,2 ,3 ].every (function (currentValue, index, arr ){ console .log (currentValue, index, arr, this ) return currentValue < 2 ; }, {a : 123 }) console .log (numberFlag)const numberFlag = [1 ,2 ,3 ].some (function (currentValue, index, arr ){ console .log (currentValue, index, arr, this ) return currentValue < 23 ; }, {a : 123 }) console .log (numberFlag)const numberFlag = [1 ,2 ,3 ].find (function (currentValue, index, arr ){ console .log (currentValue, index, arr, this ) return currentValue < 3 ; }, {a : 123 }) console .log (numberFlag)const numberFlag = [1 ,2 ,3 ].findIndex (function (currentValue, index, arr ){ console .log (currentValue, index, arr, this ) return currentValue < 3 ; }, {a : 123 }) console .log (numberFlag)const number = [1 ,2 ,3 ].reduce (function (total, currentValue, currentIndex, arr ){ console .log (total, currentValue, currentIndex, arr, this ) return total + currentValue; }, 150 ) console .log (number)const number = [1 ,2 ,3 ].reduceRight (function (total, currentValue, currentIndex, arr ){ console .log (total, currentValue, currentIndex, arr, this ) return total + currentValue; }, 150 ) console .log (number)
基础(操作符new/隐式转换) 隐式转换规则 当隐式转换为number时,在ToPrimitive中优先查找obj的valueOf方法,如果为原始值,则返回,否则调用obj的toString方法,如果为原始值则返回,否则抛出TypeError异常。
当隐式转换为string时,在ToPrimitive中优先查找toString方法,如果为原始值则返回,否则调用obj的valueOf方法,如果有原始值则返回,否则抛出TypeError异常。
如果对象为Date,默认转化为string,其他情况下默认number。
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 1 +'23' 1 +false 1 +Symbol () '1' +false false +true 1 *'23' 1 *false 1 / 'aa' 1 /0 3 == true '0' == false 'a' < 'b' 'a' < 'b' false > -1 var a = {};a > 2
操作符new new操作符的执行过程:
1、创建一个新的空对象;
2、设置原型,将对象的原型设置为函数的prototype对象;
3、让函数的this指向这个对象,执行构造函数的代码(为这个新对象添加属性);
4、判断函数的返回值类型,如果是值类型,返回创建对象;如果是引用类型,返回引用类型的对象。
小提示:箭头函数是无法new的,因为它没有prototype,也没有自己的this指向,更不可能有arguments参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function objectFactory ( ) { let newObject = null ; let constructor = Array .prototype .shift .call (arguments ); let result = null ; if (typeof constructor !== "function" ) { console .error ("type error" ); return ; } newObject = Object .create (constructor.prototype ); result = constructor.apply (newObject, arguments ); let flag = result && (typeof result === "object" || typeof result === "function" ); return flag ? result : newObject; } objectFactory (构造函数, 初始化参数);
内置对象 js内置对象主要指的是在程序执行前存在全局作用域里的由js定义的一些全局值属性、函数和用来实例化其他对象的构造函数对象。
值属性(全局属性返回一个简单值): undefined、null、Infinity、NaN字面量等;
函数属性(不需要在调用时指定所属对象,执行结束后会将结果直接返回给调用者):eval()、parseInt()、parseFloat()等;
基本对象(是定义或调用其他对象的基础,一般包括对象、函数对象和错误对象):Object、Function、Boolean、Symbol、Error等;
数字和日期对象(数字、日期对象和执行数学计算的对象):Number、Math、Date等;
字符串(用来表示和字符串操作的对象):String、RegExp等;
可索引的集合对象(表示按照索引值类排序的数据集合,包括数组和类型数组,以及类数组结构的对象):Array等;
使用键的集合对象(集合在存储数据时会使用到键,支持按照插入顺序来迭代元素):Map、Set、WeakMap、WeakSet等;
矢量集合:SIMD等;
结构化数据:JSON等;
控制抽象对象:Promise、Generator等;
反射:Proxy、Reflect等
国际化(支持多语言):Intl、Intl.Collator等;
WebAssembly:
其他:例如 arguments
位运算符 常见的位运算符 :&(与:两个位都为1时,结果才为1)、|(或)、^(异或:两个位相同为0,相异为1)、~(取反:0变1,1变0)、<<(左移:各二进制位全部左移若干位,高位丢弃,低位补0)、>>(右移:各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃);
运算规则:
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 0 & 0 0 & 1 1 & 1 1 & 0 3 & 5 0 | 0 0 | 1 1 | 0 1 | 1 3 | 5 0 ^ 0 0 ^ 1 1 ^ 0 1 ^ 1 3 ^ 5 ~ 1 ~ 0 ~ 5 1 >>2 2 >>2 10 >> 2
原码、补码、反码 计算机中的有符号数 有三种表示方法,即原码、反码和补码。三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位,三种表示方法各不相同.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 原码:1000 1010 反码:1111 0101 原码:1000 1010 反码:1111 0101 补码:1111 0110
作用域/执行上下文/闭包 闭包 指的是有权访问另一个函数作用域中的变量的函数。
创建闭包的最常见的方式是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。
闭包有两个常用的用途:一是使我们在函数外部能够访问到函数内部的变量;二是使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。
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 A ( ) { let a = 1 window .B = function ( ) { console .log (a) } } A ()B () for (var i = 1 ; i <= 5 ; i++) { (function (j ) { setTimeout (function timer ( ) { console .log (j) }, j * 1000 ) })(i) } for (var i = 1 ; i <= 5 ; i++) { setTimeout (function timer (j ) { console .log (j) }, i * 1000 , i) }
作用域/作用域链 全局作用域 :最外层函数和最外层函数外面定义的变量拥有全局作用域,所有未定义直接赋值的变量自动声明为全局作用域,所有window对象的属性拥有全局作用域,所有window对象的属性拥有全局作用域;
函数作用域 :函数作用域声明在函数内部的变零,一般只有固定的代码片段可以访问到,作用域是分层的,内层作用域可以访问外层作用域,反之不行。
块级作用域 :使用ES6中新增的let和const指令可以声明块级作用域,块级作用域可以在函数中创建也可以在一个代码块中的创建(由 { }
包裹的代码片段),使用ES6中新增的let和const指令可以声明块级作用域,块级作用域可以在函数中创建也可以在一个代码块中的创建(由 { }
包裹的代码片段),在循环中比较适合绑定块级作用域,这样就可以把声明的计数器变量限制在循环内部。
作用域链 :在当前作用域中查找所需变量,但是该作用域没有这个变量,那这个变量就是自由变量。如果在自己作用域找不到该变量就去父级作用域查找,依次向上级作用域查找,直到访问到window对象就被终止,这一层层的关系就是作用域链。
作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,可以访问到外层环境的变量和函数。 作用域链的本质上是一个指向变量对象的指针列表。
对执行上下文的理解 全局执行上下文 :任何不在函数内部的都是全局执行上下文,它首先会创建一个全局的window对象,并且设置this的值等于这个全局对象,一个程序中只有一个全局执行上下文;
函数执行上下文 :当一个函数被调用时,就会为该函数创建一个新的执行上下文,函数的上下文可以有任意多个;
eval
函数执行上下文 :执行在eval函数中的代码会有属于他自己的执行上下文,不过eval函数不常使用。
this/call/apply/bind 对this对象的理解 this 是执行上下文中的一个属性,它指向最后一次调用这个方法的对象。
在实际开发中,this 的指向可以通过四种调用模式来判断:函数调用模式 (当一个函数不是一个对象的属性时,直接作为函数来调用时,this 指向全局对象);方法调用模式 (如果一个函数作为一个对象的方法来调用时,this 指向这个对象);构造器调用模式 (如果一个函数用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象);apply 、 call 和 bind 调用模式 (这三个方法都可以显示的指定调用函数的 this 指向)
call() 和 apply() 的区别:apply 接受两个参数,第一个参数指定了函数体内 this 对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply 方法把这个集合中的元素作为参数传递给被调用的函数;call 传入的参数数量不固定,跟 apply 相同的是,第一个参数也是代表函数体内的 this 指向,从第二个参数开始往后,每个参数被依次传入函数。
call 函数的实现步骤 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 1 、判断调用对象是否为函数,即使是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。2 、判断传入上下文对象是否存在,如果不存在,则设置为 window 。3 、处理传入的参数,截取第一个参数后的所有参数。4 、将函数作为上下文对象的一个属性。5 、使用上下文对象来调用这个方法,并保存返回结果。6 、删除刚才新增的属性。7 、返回结果。Function .prototype .myCall = function (context ) { if (typeof this !== "function" ) { console .error ("type error" ); } let args = [...arguments ].slice (1 ), result = null ; context = context || window ; context.fn = this ; result = context.fn (...args); delete context.fn ; return result; };
apply 函数的实现步骤 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 1 、判断调用对象是否为函数,即使是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。2 、判断传入上下文对象是否存在,如果不存在,则设置为 window 。3 、将函数作为上下文对象的一个属性。4 、判断参数值是否传入5 、使用上下文对象来调用这个方法,并保存返回结果。6 、删除刚才新增的属性7 、返回结果Function .prototype .myApply = function (context ) { if (typeof this !== "function" ) { throw new TypeError ("Error" ); } let result = null ; context = context || window ; context.fn = this ; if (arguments [1 ]) { result = context.fn (...arguments [1 ]); } else { result = context.fn (); } delete context.fn ; return result; };
bind 函数的实现步骤 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 1 、判断调用对象是否为函数,即使是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。2 、保存当前函数的引用,获取其余传入参数值。3 、创建一个函数返回4 、函数内部使用 apply 来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的 this 给 apply 调用,其余情况都传入指定的上下文对象。Function .prototype .myBind = function (context ) { if (typeof this !== 'function' ){ console .error ('err' ) } let args = [...arguments ].slice (1 ) let fn = this const func = function ( ){ return fn.apply (this instanceof fn ? this :context, args.concat ([...arguments ])) } func.prototype = Object .create (fn.prototype ) func.prototype .constructor = func return func };
原型/原型链/继承 原型:当使用构造函数新建一个对象后,在这个对象的内部将包含一个指针,这个指针指向构造函数的 prototype 属性对应的值,在 ES5 中这个指针被称为对象的原型。
原型链:当访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。(原型链的尽头一般来说都是 Object.prototype )。
继承指的是对象的继承,指的是子对象能够使用父级对象的事件。
事件循环 1、JavaScript是单线程,非阻塞的。
2、浏览器事件循环:执行栈和事件队列;宏任务(macrotask)和微任务(microtask)。
宏任务:script(整体代码)、setTimeout()、setInterval()、postMessage、I/O、UI交互事件、MessageChannel、setImmediate(Node.js 环境);
微任务:new Promise().then(回调)、MutationObserver(html5 新特性)、Object.observe、process.nextTick(Node.js 环境)。
在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的。
运行顺序:执行一个宏任务(栈中没有就从事件队列中获取);执行过程中如果遇到微任务,就将它添加到微任务的任务队列中;宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行);当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染;渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)。
执行宏任务,然后执行该宏任务产生的微任务,若微任务在执行过程中产生了新的微任务,则继续执行微任务,微任务执行完毕后,再回到宏任务中进行下一轮循环。
3、node环境下的事件循环:表现出的状态与浏览器大致相同。不同的是 node 中有一套自己的模型。node 中事件循环的实现依赖 libuv 引擎。Node的事件循环存在几个阶段(如果是node10及其之前版本,microtask会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行 microtask队列中的任务;node版本更新到11之后,Event Loop运行原理发生了变化,一旦执行一个阶段里的一个宏任务(setTimeout,setInterval和setImmediate)就立刻执行微任务队列,跟浏览器趋于一致。
异步编程/Promise/async和await/并发/并行 JavaScript中的异步机制 可以分为以下几种:
回调函数 的方式(使用回调函数的方式有一个缺点是,多个回调函数嵌套的时候会造成回调函数地狱,上下两层的回调函数间的代码耦合度太高,不利于代码的可维护);
Promise 的方式(使用 Promise 的方式可以将嵌套的回调函数作为链式调用。但是使用这种方法,有时会造成多个 then 的链式调用,可能会造成代码的语义不够明确);
generator 的方式(它可以在函数的执行过程中,将函数的执行权转移出去,在函数外部还可以将执行权转移回来);
1 2 3 4 5 6 7 8 9 10 11 12 13 function * generatorForLoop (num ) { for (let i = 0 ; i < num; i += 1 ) { yield console .log (i, "generator" ); } } const genForLoop = generatorForLoop (5 );genForLoop.next (); genForLoop.next (); genForLoop.next (); genForLoop.next (); genForLoop.next ();
async 函数 的方式(async 函数是 generator 和 promise 实现的一个自动执行的语法糖,它内部自带执行器,当函数内部执行到一个 await 语句的时候,如果语句返回一个 promise 对象,那么函数将会等待 promise 对象的状态变为 resolve 后再继续向下执行。因此可以将异步逻辑,转化为同步的顺序来书写,并且这个函数可以自动执行)。
setTimeout、Promise、Async/Await 的区别 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 console .log ('script start' ) setTimeout (function ( ){ console .log ('settimeout' ) }) console .log ('script end' ) console .log ('script start' )let promise1 = new Promise (function (resolve ) { console .log ('promise1' ) resolve () console .log ('promise1 end' ) }).then (function ( ) { console .log ('promise2' ) }) setTimeout (function ( ){ console .log ('settimeout' ) }) console .log ('script end' )async function async1 ( ){ console .log ('async1 start' ); await async2 (); console .log ('async1 end' ) } async function async2 ( ){ console .log ('async2' ) } console .log ('script start' );async1 ();console .log ('script end' )
对Promise的理解 Promise的实例有三个状态 :Pending(进行中)、Resolved(已完成)、Rejected(已拒绝)。
优点:对象的状态不受外界影响,一旦状态改变就不会再变,任何时候都可以得到这个结果。
缺点:无法取消Promise,一旦新建它就会立即执行,无法中途取消。无法取消Promise,一旦新建它就会立即执行,无法中途取消。当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
Promise有五个常用的方法:then()、catch()、all()、race()、finally。
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 promise1 = new Promise (function (resolve, reject ) { reject ({ error : "失败" }); }); const promise2 = new Promise (function (resolve, reject ) { resolve ({ success : "成功+++1" }) }); const promise3 = new Promise (function (resolve, reject ) { reject ({ error : "失败~~~~~1" }); }); promise1.then (function (value ) { console .log (value); }, function (error ) { console .log (error) }).catch ((error ) => { console .log (error, "第二次失败" ) }); Promise .all ([promise1,promise2,promise3]).then (res => { console .log (res); }).catch ((error ) => { console .log (error); }) Promise .race ([promise1, promise2, promise3]).then (res => { console .log (res, "race" ); }, rej => { console .log (rej, "race" ) })
并发与并行的区别 并发 是宏观概念,我分别有任务 A 和任务 B,在一段时间内通过任务间的切换完成了这两个任务,这种情况就可以称之为并发。
并行 是微观概念,假设 CPU 中存在两个核心,那么我就可以同时完成任务 A、B。同时完成多个任务的情况就可以称之为并行。
Proxy Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。
new Proxy(target, handler):target为需要代理的对象,handler传的是具体的get、set方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const watchProxy = (obj ) => { return new Proxy (obj, { get (target, property, receiver ){ console .log (target, property, receiver, "get-watch" ); return Reflect .get (target, property, receiver); }, set (target, property, value, receiver ) { console .log (target, property, value, receiver, "set-watch" ); return Reflect .set (target, property, value, receiver); } }) } let p1 = watchProxy (obj);p1.a = "重新测试" console .log (p1, p1.a );
ES6新特性(常用方法区别) 1、let、const、var: 块级作用域 (let、const有;var没有)、存在变量提升 (var有变量提升;let、const没有,即在变量只能在声明之后使用,否在会报错)、添加全局属性 (var声明的变量为全局,: 浏览器的全局对象是window,Node的全局对象是global)、重复声明 (const和let不允许重复声明变量;var可以重复声明,后声明的同名变量会覆盖之前声明的遍历)、存在暂时性死区 (let、const命令声明变量之前,该变量都是不可用的;var声明的变量不存在暂时性死区)、设置初始值 (var 和 let 可以不用设置初始值;const声明变量必须设置初始值)、指针指向 ( let创建的变量是可以更改指针指向(可以重新赋值);const声明的变量是不允许改变指针的指向);
2、箭头函数与普通函数的区别 :箭头函数比普通函数更加简洁;箭头函数比普通函数更加简洁;箭头函数继承来的this指向永远不会改变;call()、apply()、bind()等方法不能改变箭头函数中this的指向(),this是捕获其所在上下⽂的 this 值,作为⾃⼰的 this 值;箭头函数不能作为构造函数使用;箭头函数没有自己的arguments;箭头函数没有prototype;箭头函数不能用作Generator函数,不能使用yeild关键字。
3、扩展运算符(…)的作用及使用场景 :对象扩展运算符(扩展运算符对对象实例的拷贝属于浅拷贝)、数组扩展运算符(数组的扩展运算符可以将一个数组转为用逗号分隔的参数序列,且每次只能展开一层数组)。
4、模板语法: 在模板字符串中,空格、缩进、换行都会被保留;模板字符串完全支持“运算”式的表达式,可以在${}里完成一些计算。
5、Map :ES6提供的Map数据结构类似于对象,但是它的键不限制范围,可以是任意类型,是一种更加完善的Hash结构。实际上Map是一个数组,它的每一个数据也都是一个数组。(有以下操作方法:size、set(key,value)、get(key)、has(key)、delete(key)、clear();遍历方法:keys()、values()、entries()、forEach())。
6、模块Module :和CommonJS模块区别(CommonJS是对模块的浅拷⻉、ES6 Module是对模块的引⽤,即ES6 Module只存只读,不能改变其值,也就是指针指向不能变,类似const;import的接⼝是read-only(只读状态),不能修改其变量值);和CommonJS模块的共同点(都可以对引⼊的对象进⾏赋值,即对对象内部属性的值进⾏改变;CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。CommonJS 模块是运行时加载,ES6 模块是编译时输出接口;CommonJs 是单个值导出,ES6 Module可以导出多个;CommonJs 是动态语法可以写在判断里,ES6 Module 静态语法只能写在顶层;CommonJs 的 this 是当前模块,ES6 Module的 this 是 undefined)。
7、for…of遍历 :ES6新增的遍历方式,允许遍历一个含有iterator接口的数据结构(数组、对象等)并且返回各项的值,普通的对象用for..of遍历是会报错的。
8、Symbol 和 BigInt :数据类型
Ajax/fetch/axios/网络协议 Ajax AJAX是 Asynchronous JavaScript and XML 的缩写,指的是通过 JavaScript 的 异步通信,从服务器获取 XML 文档从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页。
缺陷:本身是针对MVC编程,不符合前端MVVM的浪潮;基于原生XHR开发,XHR本身的架构不清晰;不符合关注分离(Separation of Concerns)的原则;配置和调用方式非常混乱,而且基于事件的异步模型不友好。
创建AJAX请求的步骤:
创建一个 XMLHttpRequest 对象。
在这个对象上使用 open 方法创建一个 HTTP 请求 ,open 方法所需要的参数是请求的方法、请求的地址、是否异步和用户的认证信息。
在发起请求前,可以为这个对象添加一些信息和监听函数 。比如说可以通过 setRequestHeader 方法来为请求添加头信息。还可以为这个对象添加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5 个状态,当它的状态变化时会触发onreadystatechange 事件,可以通过设置监听函数,来处理请求成功后的结果。当对象的 readyState 变为 4 的时候,代表服务器返回的数据接收完成,这个时候可以通过判断请求的状态,如果状态是 2xx 或者 304 的话则代表返回正常。这个时候就可以通过 response 中的数据来对页面进行更新了。
当对象的属性和监听函数设置完成后,最后调用 sent 方法来向服务器发起请求 ,可以传入参数作为发送的数据体。
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 function getJSON (url ) { let promise = new Promise (function (resolve, reject ) { let xhr = new XMLHttpRequest (); xhr.open ("GET" , url, true ); xhr.onreadystatechange = function ( ) { if (this .readyState !== 4 ) return ; if (this .status === 200 ) { resolve (this .response ); } else { reject (new Error (this .statusText )); } }; xhr.onerror = function ( ) { reject (new Error (this .statusText )); }; xhr.responseType = "json" ; xhr.setRequestHeader ("Accept" , "application/json" ); xhr.send (null ); }); return promise; }
Fetch fetch号称是AJAX的替代品,是在ES6出现的,使用了ES6中的promise对象。Fetch是基于promise设计的。Fetch的代码结构比起ajax简单多。fetch不是ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象 。
优点:语法简洁,更加语义化;基于标准 Promise 实现,支持 async/await;更加底层,提供的API丰富(request, response);脱离了XHR,是ES规范里新的实现方式。
缺点:fetch只对网络请求报错,对400,500都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject;fetch默认不会带cookie,需要添加配置项: fetch(url, {credentials: ‘include’});fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费;fetch没有办法原生监测请求的进度,而XHR可以。
Axios Axios 是一种基于Promise封装的HTTP客户端,其特点如下:
浏览器端发起XMLHttpRequests请求
node端发起http请求
支持Promise API
监听请求和返回
对请求和返回进行转化
取消请求
自动转换json数据
客户端支持抵御XSRF攻击
垃圾回收与内存泄漏 浏览器的垃圾回收机制 概念 :JavaScript代码运行时,需要分配内存空间来储存变量和值。当变量不在参与运行时,就需要系统收回被占用的内存空间,这就是垃圾回收。
回收机制 :
Javascript 具有自动垃圾回收机制,会定期对那些不再使用的变量、对象所占用的内存进行释放,原理就是找到不再使用的变量,然后释放掉其占用的内存;
JavaScript中存在两种变量:局部变量和全局变量。全局变量的生命周期会持续要页面卸载;而局部变量声明在函数中,它的生命周期从函数执行开始,直到函数执行结束,在这个过程中,局部变量会在堆或栈中存储它们的值,当函数执行结束后,这些局部变量不再被使用,它们所占有的空间就会被释放;
不过,当局部变量被外部函数使用时,其中一种情况就是闭包,在函数执行结束后,函数外部的变量依然指向函数内部的局部变量,此时局部变量依然在被使用,所以不会回收。
垃圾回收的方式 :
浏览器通常使用的垃圾回收方法有两种:标记清除,引用计数。
标记清除 :
标记清除是浏览器常见的垃圾回收方式,当变量进入执行环境时,就标记这个变量“进入环境”,被标记为“进入环境”的变量是不能被回收的,因为他们正在被使用。当变量离开环境时,就会被标记为“离开环境”,被标记为“离开环境”的变量会被内存释放。
垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后。垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间。
引用计数
另外一种垃圾回收机制就是引用计数,这个用的相对较少。引用计数就是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减1。当这个引用次数变为0时,说明这个变量已经没有价值,因此,在在机回收期下次再运行时,这个变量所占有的内存空间就会被释放出来。
这种方法会引起循环引用的问题:例如: obj1和 obj2通过属性进行相互引用,两个对象的引用次数都是2。当使用循环计数时,由于函数执行完后,两个对象都离开作用域,函数执行结束,obj1和 obj2还将会继续存在,因此它们的引用次数永远不会是0,就会引起循环引用。
1 2 3 4 5 6 7 8 9 function fun ( ) { let obj1 = {}; let obj2 = {}; obj1.a = obj2; obj2.a = obj1; } obj1.a = null obj2.a = null
减少垃圾回收 :
虽然浏览器可以进行垃圾自动回收,但是当代码比较复杂时,垃圾回收所带来的代价比较大,所以应该尽量减少垃圾回收。
对数组进行优化: 在清空一个数组时,最简单的方法就是给其赋值为[ ],但是与此同时会创建一个新的空对象,可以将数组的长度设置为0,以此来达到清空数组的目的。
对object进行优化: 对象尽量复用,对于不再使用的对象,就将其设置为null,尽快被回收。
对函数进行优化: 在循环中的函数表达式,如果可以复用,尽量放在函数的外面。
内存泄漏 以下四种情况会造成内存的泄漏:
意外的全局变量: 由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。
被遗忘的计时器或回调函数: 设置了 setInterval 定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。
脱离 DOM 的引用: 获取一个 DOM 元素的引用,而后面这个元素被删除,由于一直保留了对这个元素的引用,所以它也无法被回收。
闭包: 不合理的使用闭包,从而导致某些变量一直被留在内存当中。
常见问题 0.1+0.2 !== 0.3如何让其相等 精度丢失问题
1 2 3 4 5 function numberepsilon (arg1, arg2 ){ return Math .abs (arg1 - arg2) < Number .EPSILON ; } console .log (numberepsilon (0.1 +0.2 , 0.3 ))
附录:
JavaScript代码的三种引入方式【操作演示】 - 知乎 (zhihu.com)
JavaScript篇_w3cschool
33. SIMD - 概述 - 《阮一峰 ECMAScript 6 (ES6) 标准入门教程 第三版》 - 书栈网 · BookStack
JavaScript事件循环机制解析 - 简书 (jianshu.com)
JS 常用的六种设计模式介绍 - 掘金 (juejin.cn)
JavaScript设计模式es6(23种) - 掘金 (juejin.cn)
10分钟掌握JavaScript设计模式 - 掘金 (juejin.cn)