当前位置: 首页 > news >正文

网站建设论文二稿点的排版设计网站

网站建设论文二稿,点的排版设计网站,网站开发图书系统前台模板,南京在线网站制作​ #x1f308;个人主页#xff1a;前端青山 #x1f525;系列专栏#xff1a;JavaScript篇 #x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来JavaScript篇专栏内容:JavaScript全面指南(一) 1、介绍一下JS的内置类型有哪些#xff1f; 基本数据类型…​ 个人主页前端青山 系列专栏JavaScript篇 人终将被年少不可得之物困其一生 依旧青山,本期给大家带来JavaScript篇专栏内容:JavaScript全面指南(一) 1、介绍一下JS的内置类型有哪些 基本数据类型Undefined Null Boolean Number String 内置对象Object是Javascript中所有对象的父对象 ​ 数据封装对象Object Array Boolean Number String ​ 其他对象Function Argument Math Date RegExp Error 2、介绍一下 typeof 区分类型的原理 instanceof的实现代码: // L instanceof R function instance_of(L, R) {//L 表示左表达式R 表示右表达式var O R.prototype;// 取 R 的显式原型L L.__proto__; // 取 L 的隐式原型while (true) { if (L null) //已经找到顶层return false; if (O L) //当 O 严格等于 L 时返回 truereturn true; L L.__proto__; //继续向上一层原型链查找} }首先typeof 能够判断基本数据类型但是除了nulltypeof null 返回的是object 但是对于对象来说typeof不能准确判断类型typeof 函数会返回function除此之外全部都是object不能准确判断类型 instanceof可以判断复杂数据类型基本数据类型不可以 instanceof是通过原型链来判断的 A instanceof B在A的原型链中层层查找是否有原型等于B.prototype如果一直找到A的原型链的顶端null即Object.prototype.proto仍然不等于B那么返回false否则返回true 3、JavaScript 中的强制转型是指什么 在 JavaScript 中两种不同的内置类型间的转换被称为强制转型。强制转型在 JavaScript 中有两种形式显式和隐式。 这是一个显式强制转型的例子 var a 42; var b Number( a ); a; // 42 b; // 42 -- 是个数字!这是一个隐式强制转型的例子 var a 42; var b a * 1; // 42 隐式转型成 42 a; // 42 b; // 42 -- 是个数字!4、说说你对javascript的作用域的理解 前言 学习 JavaScript 也有一段时间今天抽空总结一下作用域也方便自己以后翻阅。 什么是作用域 如果让我用一句简短的话来讲述什么是作用域我的回答是 其实作用域的本质是一套规则它定义了变量的可访问范围控制变量的可见性和生命周期。 既然作用域是一套规则那么究竟如何设置这些规则呢 先不急在这之前我们先来理解几个概念。 编译到执行的过程 下面我们就拿这段代码来讲述 JavaScript 编译到执行的过程。 var a 2;首先我们来看一下在这个过程中几个功臣所需要做的事。 引擎总指挥 从头到尾负责整个 JavaScript 程序的编译及执行过程。 编译器劳工词法分析分词 解析成词法单元var、a、、2。语法分析解析 将单词单元转换成抽象语法树AST。代码生成 将抽象语法树转换成机器指令。作用域仓库管理员 负责收集并维护所有生命的标识符变量组成的一系列查询并实施一套非常严格的规则确定当前执行的代码对这些标识符的访问权限。 然后我们再来看执行这段代码时每个功臣是怎么协同工作的。 引擎 其实这段代码有两个完全不同的声明var a和a 2一个由编译器在编译时处理另一个则由引擎在运行时处理。 编译器 一套编译器常规操作下来到代码生成步骤。遇到var a会先询问作用域中是否已经存在同名变量如果是则忽略该声明继续进行编译否则它会要求作用域声明一个新的变量a。为引擎生成运行a 2时所需的代码。 引擎 会先询问作用域是否存在变量a如果是就会使用这个变量进行赋值操作否则一直往外层嵌套作用域找详见作用域嵌套直至到全局作用域都没有时抛出一个异常。 **总结**变量的赋值操作会执行两个动作 首先编译器会在当前作用域中声明一个变量 如果之前没有声明过然后在运行时引擎会在作用域中查找该变量 如果能够找到就会对它赋值。 LHS RHS 查询 从上面可知引擎在获得编译器给出的代码后还会对作用域进行询问变量。 聪明的你肯定一眼就看出L和R的含义它们分别代表左侧和右侧。 现在我们把代码改成这样 var a b;这时引擎对a进行 LHS 查询对b进行 RHS 查询但是L和R并不一定指操作符的左右边而应该这样理解 LHS 是为了找到赋值的目标。 RHS 是赋值操作的源头。也就是 LHS 是为了找到变量这个容器本身给它赋值而 RHS 是为了取出这个变量的值。 作用域嵌套 当一个块或函数嵌套在另一个块或函数中时就发生了作用域的嵌套进而形成了一条作用域链。因此在当前作用域中无法找到某个变量时引擎就会在外层嵌套的作用域中继续查找直到找到该变量 或抵达最外层的作用域(也就是全局作用域)为止。 词法作用域 作用域分为两种 词法作用域较为普遍JavaScript所使用的也是这种动态作用域使用较少比如 Bash 脚本、Perl 中的一些模式等 词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的。 看以下代码这个例子中有三个逐级嵌套的作用域。 var a 2; // 作用域1 全局 function foo(){ var b a * 2; // 作用域2 局部function bar(){var c a * b; // 作用域3 局部} }作用域是由你书写代码所在位置决定的。子级作用域可以访问父级作用域而父级作用域则不能访问子级作用域。 引擎对作用域的查找 作用域查找会在找到第一个匹配的标识符时停止在多层的嵌套作用域中可以定义同名的标识符这叫做“遮蔽效应”内部的标识符“遮蔽”了外部的标识符。也就是说查找时会从运行所在的作用域开始逐级往上查找直到遇见第一个标识符为止。 全局变量全局作用域下定义的变量会自动变成全局对象比如浏览器中的 window对象。 var a 1; function foo(){var a 2;console.log(a); // 2function bar(){var a 3;console.log(a); // 3console.log(window.a); // 1} }非全局的变量如果被遮蔽了就无论如何都无法被访问到所以在上述代码中bar内的作用域无法访问到foo下定义的变量a。 词法作用域查找只会查找一级标识符比如a、b如果是foo.bar词法作用域查找只会试图查找foo标识符找到这个变量后由对象属性访问规则接管属性的访问。 欺骗语法 虽然词法作用域是在代码编写时确定的但还是有方法可以在引擎运行时动态修改词法作用域有两种机制 evalwith eval JavaScript 的 eval函数可以接受一个字符串参数并作为代码语句来执行 就好像代码是原本就在那个位置一样考虑以下代码 function foo(str){eval(str) // 欺骗console.log(a); } var a 1; foo(var a 2;); // 2仿佛eval中传入的参数语句原本就在那一样会创建一个变量a并遮蔽了外部作用域的同名变量。 注意 eval通常被用来执行动态创建的代码可以根据程序逻辑动态地将变量和函数以字符串形式拼接在一起之后传递进去。在严格模式下eval无法修改所在的作用域。与eval相似的还有setTimeout、setInterval、new Function。 with with通常被当作重复引用同一个对象中的多个属性的快捷方式 可以不需要重复引用对象本身。 使用方法如下 var obj1 { a:1,b:2 }; function foo(obj){with(obj){a 2;b 3;} } foo(obj1); console.log(obj1); // {a: 2, b: 3}然而考虑以下代码 var obj2 { a:1,b:2 }; function foo(obj){with(obj){a 2;b 3;c 4;} } foo(obj2); console.log(obj2); // {a: 2, b: 3} console.log(c); // 4 不好c被泄露到全局作用域下尽管with可以将对象处理为词法作用域但是这样块内部正常的var操作并不会限制在这个块的作用域下而是被添加到with所在的函数作用域下而不通过var声明变量将视为声明全局变量。 性能 eval和with会在运行时修改或创建新的作用域以此来欺骗其他书写时定义的词法作用域然而 JavaScript 引擎会在编译阶段进行性能优化有些优化依赖于能够根据代码的词法进行静态分析并预先确定所有的变量和函数的定义位置才能在执行过程中快速找到标识符。但是通过eval和with来欺骗词法作用域会导致引擎无法知道他们对词法作用域做了什么样的改动只能对部分不进行优化因此如果在代码中大量使用eval或with就会导致代码运行起来变得非常慢。 函数作用域和块作用域 函数作用域 在 JavaScript 中每声明一个函数就会创建一个函数作用域同时属于这个函数的所有变量在整个函数的范围内都可以使用。 块作用域 从 ES3 发布以来JavaScript 就有了块作用域创建块作用域的几种方式有 with 上面已经讲了这里不再复述。 try/catch try/catch 的 catch 分句会创建一个块作用域其中声明的变量仅在 catch 内部有效。 javascript try{ throw 2; }catch(a){ console.log(a); }let和const ES6 引入的新关键词提供了除 var 以外的变量声明方式它们可以将变量绑定到所在的任意作用域中通常是{}内部。 javascript { let a 2; } console.log(a); // ReferenceError: a is not defined注意使用 let和const 进行的声明不会在块作用域中进行提升。 提升 考虑这段代码 console.log( a ); var a 2;输入结果是undefined而不是ReferenceError。 为什么呢 前面说过编译阶段时会把声明分成两个动作也就是只把var a部分进行提升。 所以第二段代码真正的执行顺序是 var a; // 这时 a 是 undefined console.log(a); a 2;编译阶段时会把所有的声明操作提升而赋值操作原地执行。函数声明会把整个函数提升而不仅仅是函数名。 函数优先 虽然函数和变量都会被提升但函数声明的优先级高于变量声明所以 foo(); // 1 var foo; function foo(){console.log(1); } foo function(){console.log(2); }因为这个代码片段会被引擎理解为如下形式 function foo(){console.log(1); } foo(); // 1 foo function() { console.log( 2 ); };这个值得一提的是尽管var foo出现在function foo()...之前但由于函数声明会被优先提升所以它会被忽略因为重复声明了。 注意 JavaScript 会忽略前面已经声明过的声明不管它是变量还是函数只要其名称相同。 后记 5、什么是作用域链 在了解作用域链之前先来了解下什么是作用域 作用域scope通常来说一段程序代码中所用到的名字并不总是有效/可用的而限定这个名字的可用性的代码范围就是这个名字的作用域。 js是没有块级作用域的也就是说外面可以使用里面的变量包括do while/for中的。 例如 for ( var i0; i10; i ) {var a3 } console.log(a)//3但函数内定义的变量函数外是不可以使用的例如console.log(a),不能使用fn()里的var a1,没有声明变量就会报错。 function fn() {var a1; } console.log(a);//ReferenceError:a is not defined但函数里面的却可以使用外面的变量这就是引申出出了作用域链。 作用域链 函数在执行的过程中先从自己内部找变量如果找不到再从创建当前函数所在的作用域去找, 以此往上注意找的是变量的当前的状态。 例如 var a 1 function fn1(){function fn2(){console.log(a)}function fn3(){var a 4fn2()}var a 2return fn3 } var fn fn1() fn() //输出2解析var fn fn1(),执行函数fn1(),因为return fn3再执行函数fn3(),再执行函数fn2(),再执行console.log(a),那么a是多少呢fn2里没有变量a去fn2的上一级声明fn2的地方fn1里去找找到了 var a2, 那么console.log(2)输出了2。 6、解释下 let 和 const 的块级作用域 在ES6之前我们都是用 var 关键字声明变量。无论声明在何处都会被视为声明在函数的最顶部(不在函数内即在全局作用域的最顶部)。这就是函数变量提升 例如 function aa() {if(flag) {var test hello man} else {console.log(test)}}12345678变量声明后代码实际上是 function aa() {var test // 变量提升函数最顶部if(flag) {test hello man} else {//此处访问 test 值为 undefinedconsole.log(test)}//此处访问 test 值为 undefined} 12345678910所以不用关心 flag 是否为 true or false。实际上无论如何 test 都会被创建声明。 接下来ES6主角登场 我们通常用 let 和 const 来声明let 表示变量、const 表示常量。let 和 const 都是块级作用域。怎么理解这个块级作用域 在一个函数内部 在一个代码块内部 只要在 {}花括号内 的代码块即可以认为 let 和 const 的作用域。 function aa() {if(flag) {let test hello man} else {//test 在此处访问不到console.log(test)}} 12345678et 的作用域是在它所在当前代码块但不会被提升到当前函数的最顶部。 再来说说 const const 声明的变量必须提供一个值而且会被认为是常量意思就是它的值被设置完成后就不能再修改了。 const name ccname yy // 再次赋值此时会报错 12还有如果 const 的是一个对象对象所包含的值是可以被修改的。抽象一点儿说就是对象所指向的地址不能改变而变量成员是可以修改的。 看以下例子就非常清楚 const student { name: cc }student.name yy // 修改变量成员一点儿毛病没有student { name: yy } // 修改变量绑定这样子就会报错了 12345let和const命令这个区块对这些命令声明的变量从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量就会报错。 总之在代码块内使用let命令声明变量之前该变量都是不可用的。这在语法上称为“暂时性死区”temporal dead zone简称TDZ。 if (true) {// TDZ开始tmp abc; // ReferenceErrorconsole.log(tmp); // ReferenceErrorlet tmp; // TDZ结束console.log(tmp); // undefinedtmp 123;console.log(tmp); // 123typeof x; // ReferenceErrorlet x;typeof undeclared_variable // undefined1234567891011121314151617上面代码中在let命令声明变量tmpx之前都属于变量tmp,x的“死区”。只要用到该变量就会报错. undeclared_variable是一个不存在的变量名结果返回“undefined”。 function bar(x y, y 2) {return [x, y]; }bar(); // 报错123456上面代码中调用bar函数之所以报错某些实现可能不报错是因为参数x默认值等于另一个参数y而此时y还没有声明属于”死区“。如果y的默认值是x就不会报错因为此时x已经声明了。 function bar(x 2, y x) {return [x, y]; } bar(); // [2, 2]7、什么是JavaScript这是基本题对很多程序员来说也是送分题 JavaScript是客户端和服务器端脚本语言可以插入到HTML页面中并且是目前较热门的Web开发语言。同时JavaScript也是面向对象编程语言。 8、对闭包的看法为什么要用闭包说一下闭包的原理以及应用场景 一、什么是闭包 闭包就是能够读取其他函数内部变量的函数。例如在javascript中只有函数内部的子函数才能读取局部变量所以闭包可以理解成“定义在一个函数内部的函数“。在本质上闭包是将函数内部和函数外部连接起来的桥梁。 函数执行后返回结果是一个内部函数并被外部变量所引用如果内部函数持有被执行函数作用域的变量即形成了闭包。 可以在内部函数访问到外部函数作用域。使用闭包一可以读取函数作用域中的变量二可以将函数中的变量存储到内存中保护变量不被污染。而正因闭包会把函数中的变量值存储到内存中会对内存有消耗所以不能滥用闭包否则会影响网页性能造成内存泄露。当不需要使用闭包时要及时释放内存可将内存函数对象的的变量赋值为 null。 二、闭包原理 函数执行分为两个阶段预编译阶段和执行阶段。 在预编译阶段如果发现内部函数使用了外部函数的变量则会在内存中创建一个“闭包”对象并保持对应变量值如果已存在“闭包”则只需要增加对应的属性值即可。执行完后函数执行上下文会被销毁函数对“闭包”对象的引用也会销毁但其内部函数还持有对该“闭包”的引用所以内部函数还可以继续使用“外部函数”中的变量。 利用函数作用域链的特性一个函数内部定义的函数会将包含外部函数的活动对象添加到他的作用域链中函数执行完毕其执行作用域链被销毁但其因内部函数的作用域链仍然在引用这个活动对象所以其活动对象不会被销毁直到内部函数被销毁后才销毁。 三、优点 1.可以从内部函数访问外部函数的作用域中的变量且访问到的变量长期驻扎在内存中可供之后使用 2.避免变量污染全局变量 3.把变量存到独立的作用域中作为私有成员存在 四、缺点 1.对内存消耗有负面影响。因内部函数保存了对外部函数变量的引用导致无法被垃圾回收增大内存使用量所以使用不当会造成内存泄露 2.对处理速度有负面影响。闭包的层级决定了引用的外部变量在查找时经过的作用域链长度 3.可能获取到意外的值 五、应用场景 场景一典型的应用是模块封装在各模块规范出来之前都是用这样的方式阻止变量污染全局。 var fn (function(){//这样声明为模块的私有变量外界无法直接访问var foo 1;function fn(){};fn.prototype.bar function(){return foo;}return fn;}());场景二在循环中创建闭包防止取到意外的值。如下无论哪个元素触发事件都会弹出3。因为函数执行后引用 i 是同一个而 i 在循环结束后就是 3 for(var i0;i3;i){document.getElementById(idi).onclick function(){alert(i);};}//可用闭包解决function makeCallbak(num){return function(){alert(num);};}for(var i0;i3;i){document.getElementById(idi).onclick makeCallbal(i);}9、列举Java和JavaScript之间的区别 Java是一门十分完整、成熟的编程语言。相比之下JavaScript是一个可以被引入HTML页面的编程语言。这两种语言并不完全相互依赖而是针对不同的意图而设计的。 Java是一种面向对象编程OOPS或结构化编程语言类似的如C 或C而JavaScript是客户端脚本语言它被称为非结构化编程。 10、如何确定this指向 如果用一句话说明 this 的指向那么即是: 谁调用它this 就指向谁。 但是仅通过这句话我们很多时候并不能准确判断 this 的指向。因此我们需要借助一些规则去帮助自己 this 的指向可以按照以下顺序判断: 全局环境中的 this 浏览器环境无论是否在严格模式下在全局执行环境中在任何函数体外部this 都指向全局对象 window; node 环境无论是否在严格模式下在全局执行环境中在任何函数体外部this 都是空对象 {}; 是否是 new 绑定 如果是 new 绑定并且构造函数中没有返回 function 或者是 object那么 this 指向这个新对象。如下: 构造函数返回值不是 function 或 object。new Super() 返回的是 this 对象。 构造函数返回值是 function 或 objectnew Super()是返回的是Super种返回的对象。 函数是否通过 call,apply 调用或者使用了 bind 绑定如果是那么this绑定的就是指定的对象【归结为显式绑定】。 这里同样需要注意一种特殊情况如果 call,apply 或者 bind 传入的第一个参数值是 undefined 或者 null严格模式下 this 的值为传入的值 null /undefined。非严格模式下实际应用的默认绑定规则this 指向全局对象(node环境为global浏览器环境为window) 隐式绑定函数的调用是在某个对象上触发的即调用位置上存在上下文对象。典型的隐式调用为: xxx.fn() 默认绑定在不能应用其它绑定规则时使用的默认规则通常是独立函数调用。 非严格模式 node环境执行全局对象 global浏览器环境执行全局对象 window。 严格模式执行 undefined 箭头函数的情况 箭头函数没有自己的this继承外层上下文绑定的this。 11、改变this指向的方式有哪些 call、apply、bind三者为改变this指向的方法。 共同点第一个参数都为改变this的指针。若第一参数为null/undefinedthis默认指向window call无数个参数 第一个参数改变this指向第二个参数实参使用之后会自动执行该函数 function fn(a,b,c){console.log(this,abc); // this指向window}fn();fn.call(document,1,2,3);//call改变之后this指向document //输出 #document 6 1,2,3是实参 结果相加为6apply两个参数 第一个参数改变this指向第二个参数数组里面为实参使用时候会自动执行函数 function fn(a,b,c){console.log(this,abc); }fn();fn.apply(document,[1,2,3]); bind无数个参数 第一个参数改变this指向第二个参数之后实参返回值为一个新的函数使用的时候需要手动调用下返回 的新函数不会自动执行 function fn(a,b,c){console.log(this,abc); //window } let ff fn.bind(小明,1,2,3); //手动调用一下call、apply与bind区别前两个可以自动执行bind不会自动执行需要手动调用 call、bind与apply区别前两个都有无数个参数apply只有两个参数而且第二个参数为数组 12、 箭头函数的this 1.普通函数的this指向它的调用者如果没有调用者则默认指向window. 2.箭头函数的this: 指向箭头函数定义时所处的对象而不是箭头函数使用时所在的对象默认使用父级的this. 13、谈一下你对原型链的理解画一个经典的原型链图示 具体参考 这篇文章 https://note.youdao.com/ynoteshare1/index.html?id0c454a4f45787988f5b2e6f7043f70e7typenote 14、举例说明JS如何实现继承 js继承总共分成5种包括构造函数式继承、原型链式继承、组合式继承、寄生式继承和寄生组合式继承。 构造函数式继承 首先来看第一种构造函数式继承顾名思义也就是利用函数去实现继承 假设我们现在有一个父类函数 // 父类构造函数 function Parent(color) {this.color color;this.print function() {console.log(this.color);} }现在要编写一个子类函数来继承这个父类如下: // 子类构造函数 function Son(color) {Parent.call(this, color); }上面代码可以看到子类Son是通过Parent.call的方式去调用父类构造函数然后把this对象传进去执行父类构造函数之后子类Son就拥有了父类定义的color和print方法。调用一下该方法输出如下 // 测试 var son1 new Son(red); son1.print(); // redvar son2 new Son(blue); son2.print(); // blue可以看到son1和son2都正常继承了父类的print方法和各自传进去的color属性 以上就是构造函数式继承的实现了这是最原始的js实现继承的方式 但是当我们深入想一下会发现这种根本就不是传统意义上的继承 因为每一个Son子类调用父类生成的对象都是各自独立的也就是说如果父类希望有一个公共的属性是所有子类实例共享的话是没办法实现的。什么意思呢来看下面的代码 function Flower() {this.colors [黄色, 红色];this.print function () {console.log(this.colors)} }function Rose() {Flower.call(this); }var r1 new Rose(); var r2 new Rose();console.log(r1.print()); // [ 黄色, 红色 ] console.log(r2.print()); // [ 黄色, 红色 ]我们现在有一个基类Flower它有一个属性colors现在我们把某一个实例的colors值改一下 r1.colors.push(紫色);console.log(r1.print()); // [ 黄色, 红色, 紫色 ] console.log(r2.print()); // [ 黄色, 红色 ]结果如上显然改变的只有r1的值因为通过构造函数创造出来的实例对象中所有的属性和方法都是实例内部独立的并不会跟其他实例共享。 总结一下构造函数的优缺点 优点所有的基本属性独立不会被其他实例所影响缺点所有希望共享的方法和属性也独立了没有办法通过修改父类某一处来达到所有子实例同时更新的效果同时每次创建子类都会调用父类构造函数一次所以每个子实例都拷贝了一份父类函数的内容如果父类很大的话会影响性能 原型链继承 下面我们来看第二种继承方式原型链式继承 同样先来看下例子 function Parent() {this.color red;this.print function() {console.log(this.color);} } function Son() { }我们有一个父类和一个空的子类 Son.prototype new Parent(); Son.prototype.constructor Son;接着我们把子函数的原型属性赋值给了父函数的实例 var son1 new Son(); son1.print(); // red最后新建子类实例调用父类的方法成功拿到父类的color和print属性方法 我们重点来分析一下下面两行代码 Son.prototype new Parent(); Son.prototype.constructor Son;这段代码中子函数的原型赋给了父函数的实例我们知道prototype是函数中的一个属性js的一个特性就是**如果一个对象某个属性找不到会沿着它的原型往上去寻找直到原型链的最后才会停止寻找。**关于原型更多基础的知识可以参考一下其他文章或许以后我也会出一期专门讲解原型和原型链的文章。 回到代码我们看到最后实例son成功调用了Print方法输出了color属性这是因为son从函数Son的prototype属性上面去找到的也就是从new Parent这个对象里面找到的 这种方式也不是真正的继承因为所有的子实例的属性和方法都在父类同一个实例上了所以一旦某一个子实例修改了其中的方法其他所有的子实例都会被影响来看下代码 function Flower() {this.colors [黄色, 红色];this.print function () {console.log(this.colors)} }function Rose() {} Rose.prototype new Flower(); Rose.prototype.constructor Rose;var r1 new Rose(); var r2 new Rose();console.log(r1.print()); // [ 黄色, 红色 ] console.log(r1.print()); // [ 黄色, 红色 ]r1.colors.push(紫色);console.log(r1.print()); // [ 黄色, 红色, 紫色 ] console.log(r2.print()); // [ 黄色, 红色, 紫色 ]还是刚才的例子这次Rose子类选择了原型链继承所以子实例r1修改了colors之后r2实例的colors也被改动了这就是原型链继承不好的地方。 来总结下原型链继承的优缺点 优点很好的实现了方法的共享缺点正是因为什么都共享了所以导致一切的属性都是共享的只要某一个实例进行修改那么所有的属性都会变化 组合式继承 这里来介绍第三种继承方式组合式继承 这种继承方式很好理解既然构造函数式继承和原型链继承都有各自的优缺点那么我们把它们各自的优点整合起来不就完美了吗 组合式继承做的就是这个事情~来看一段代码例子 function Parent(color) {this.color color; } Parent.prototype.print function() {console.log(this.color); } function Son(color) {Parent.call(this, color); } Son.prototype new Parent(); Son.prototype.constructor Son;var son1 new Son(red); son1.print(); // redvar son2 new Son(blue); son2.print(); // blue上面代码中在Son子类中使用了Parent.call来调用父类构造函数同时又将Son.prototype赋给了父类实例为什么要这样做呢为什么这样就能解决上面两种继承的问题呢 我们接着分析一下使用Parent.call调用了父类构造函数之后那么以后所有通过new Son创建出来的实例就单独拷贝了一份父类构造函数里面定义的属性和方法这是前面构造函数继承所提到的一样的原理 然后再把子类原型prototype赋值给父类的实例这样**所有子类的实例对象就可以共享父类原型上定义的所有属性和方法。**这也不难理解因为子实例会沿着原型链去找到父类函数的原型。 因此只要我们定义父类函数的时候**将私有属性和方法放在构造函数里面将共享属性和方法放在原型上**就能让子类使用了。 以上就是组合式继承它很好的融合了构造函数继承和原型链继承发挥两者的优势之处因此它算是真正意义上的继承方式。 寄生式继承 既然上面的组合式继承都已经这么完美了为什么还需要其他的继承方式呢 我们细想一下Son.prototype new Parent();这行代码它有什么问题没有 显然每次我们实例化子类的时候都需要调用一次父类构造函数那么如果父类构造函数是一个很大很长的函数那么每次实例化子类就会执行很长时间。 实际上我们并不需要重新执行父类函数我们只是想要继承父类的原型。 寄生式继承就是在做这个事情它是基于原型链式继承的改良版 var obj {color: red,print: function() {console.log(this.color);} };var son1 Object.create(obj); son1.print(); // redvar son2 Object.create(obj); son2.print(); // red寄生式继承本质上还是原型链继承Object.create(obj);方法意思是以obj为原型构造对象所以寄生式继承不需要构造函数但是同样有着原型链继承的优缺点也就是它把所有的属性和方法都共享了。 寄生组合式继承 接下来到我们最后一个继承方式也就是目前业界最为完美的继承解决方案寄生组合式继承。 没错它就是es6的class语法实现原理。 但是如果你理解了组合式继承那么理解这个方式也很简单只要记住它出现的主要目的是为了解决组合式继承中每次都需要new Parent导致的执行多一次父类构造函数的缺点。 下面来看代码 function Parent(color) {this.color color; } Parent.prototype.print function() {console.log(this.color); } function Son(color) {Parent.call(this, color); } Son.prototype Object.create(Parent.prototype); Son.prototype.constructor Son;var son1 new Son(red); son1.print(); // redvar son2 new Son(blue); son2.print(); // blue这段代码不同之处只有一个就是把原来的Son.prototype new Parent();修改为了Son.prototype Object.create(Parent.prototype); 我们前面讲过Object.create方法是以传入的对象为原型创建一个新对象创建了这个新对象之后又赋值给了Son.prototype因此Son的原型最终指向的其实就是父类的原型对象和new Parent是一样的效果 到这里我们5中js的继承方式也就讲完了 15、什么是负无穷大 负无穷大是JavaScript中的一个数字可以通过将负数除以零来得到。 16、你对事件循环有了解吗说说看 我们都知道javascript从诞生之日起就是一门单线程的非阻塞的脚本语言。这是由其最初的用途来决定的与浏览器交互。 单线程意味着javascript代码在执行的任何时候都只有一个主线程来处理所有的任务。 而非阻塞则是当代码需要进行一项异步任务无法立刻返回结果需要花一定时间才能返回的任务如I/O事件的时候主线程会挂起pending这个任务然后在异步任务返回结果的时候再根据一定规则去执行相应的回调。 单线程是必要的也是javascript这门语言的基石原因之一在其最初也是最主要的执行环境——浏览器中我们需要进行各种各样的dom操作。试想一下 如果javascript是多线程的那么当两个线程同时对dom进行一项操作例如一个向其添加事件而另一个删除了这个dom此时该如何处理呢因此为了保证不会 发生类似于这个例子中的情景javascript选择只用一个主线程来执行代码这样就保证了程序执行的一致性。 当然现如今人们也意识到单线程在保证了执行顺序的同时也限制了javascript的效率因此开发出了web worker技术。这项技术号称让javascript成为一门多线程语言。 然而使用web worker技术开的多线程有着诸多限制例如所有新线程都受主线程的完全控制不能独立执行。这意味着这些“线程” 实际上应属于主线程的子线程。另外这些子线程并没有执行I/O操作的权限只能为主线程分担一些诸如计算等任务。所以严格来讲这些线程并没有完整的功能也因此这项技术并非改变了javascript语言的单线程本质。 可以预见未来的javascript也会一直是一门单线程的语言。 话说回来前面提到javascript的另一个特点是“非阻塞”那么javascript引擎到底是如何实现的这一点呢答案就是今天这篇文章的主角——event loop事件循环。 注虽然nodejs中的也存在与传统浏览器环境下的相似的事件循环。然而两者间却有着诸多不同故把两者分开单独解释。 *正文* 浏览器环境下js引擎的事件循环机制 1.执行栈与事件队列 当javascript代码执行的时候会将不同的变量存于内存中的不同位置堆heap和栈stack中来加以区分。其中堆里存放着一些对象。而栈中则存放着一些基础类型变量以及对象的指针。 但是我们这里说的执行栈和上面这个栈的意义却有些不同。 我们知道当我们调用一个方法的时候js会生成一个与这个方法对应的执行环境context又叫执行上下文。这个执行环境中存在着这个方法的私有作用域上层作用域的指向方法的参数这个作用域中定义的变量以及这个作用域的this对象。 而当一系列方法被依次调用的时候因为js是单线程的同一时间只能执行一个方法于是这些方法被排队在一个单独的地方。这个地方被称为执行栈。 当一个脚本第一次执行的时候js引擎会解析这段代码并将其中的同步代码按照执行顺序加入执行栈中然后从头开始执行。如果当前执行的是一个方法那么js会向执行栈中添加这个方法的执行环境然后进入这个执行环境继续执行其中的代码。当这个执行环境中的代码 执行完毕并返回结果后js会退出这个执行环境并把这个执行环境销毁回到上一个方法的执行环境。。这个过程反复进行直到执行栈中的代码全部执行完毕。 2.macro task与micro task 以上的事件循环过程是一个宏观的表述实际上因为异步任务之间并不相同因此他们的执行优先级也有区别。不同的异步任务被分为两类微任务micro task和宏任务macro task。 以下事件属于宏任务 setInterval()setTimeout() 以下事件属于微任务 new Promise()new MutaionObserver() 前面我们介绍过在一个事件循环中异步事件返回结果后会被放到一个任务队列中。然而根据这个异步事件的类型这个事件实际上会被对应的宏任务队列或者微任务队列中去。并且在当前执行栈为空的时候主线程会 查看微任务队列是否有事件存在。如果不存在那么再去宏任务队列中取出一个事件并把对应的回到加入当前执行栈如果存在则会依次执行队列中事件对应的回调直到微任务队列为空然后去宏任务队列中取出最前面的一个事件把对应的回调加入当前执行栈…如此反复进入循环。 我们只需记住当当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件然后再去宏任务队列中取出一个事件。同一次事件循环中微任务永远在宏任务之前执行。 这样就能解释下面这段代码的结果 setTimeout(function () {console.log(1); });new Promise(function(resolve,reject){console.log(2)resolve(3) }).then(function(val){console.log(val); })结果为 2 3 1 node环境下的事件循环机制 1.与浏览器环境有何不同? 在node中事件循环表现出的状态与浏览器中大致相同。不同的是node中有一套自己的模型。node中事件循环的实现是依靠的libuv引擎。我们知道node选择chrome v8引擎作为js解释器v8引擎将js代码分析后去调用对应的node api而这些api最后则由libuv引擎驱动执行对应的任务并把不同的事件放在不同的队列中等待主线程执行。 因此实际上node中的事件循环存在于libuv引擎中。 2.事件循环模型 下面是一个libuv引擎中的事件循环的模型: ┌───────────────────────┐ ┌─│ timers │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ I/O callbacks │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ idle, prepare │ │ └──────────┬────────────┘ ┌───────────────┐ │ ┌──────────┴────────────┐ │ incoming: │ │ │ poll │──connections─── │ │ └──────────┬────────────┘ │ data, etc. │ │ ┌──────────┴────────────┐ └───────────────┘ │ │ check │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ └──┤ close callbacks │└───────────────────────┘注模型中的每一个方块代表事件循环的一个阶段 这个模型是node官网上的一篇文章中给出的我下面的解释也都来源于这篇文章。我会在文末把文章地址贴出来有兴趣的朋友可以亲自与看看原文。 3.事件循环各阶段详解 从上面这个模型中我们可以大致分析出node中的事件循环的顺序 外部输入数据–轮询阶段(poll)–检查阶段(check)–关闭事件回调阶段(close callback)–定时器检测阶段(timer)–I/O事件回调阶段(I/O callbacks)–闲置阶段(idle, prepare)–轮询阶段… 以上各阶段的名称是根据我个人理解的翻译为了避免错误和歧义下面解释的时候会用英文来表示这些阶段。 这些阶段大致的功能如下 timers: 这个阶段执行定时器队列中的回调如 setTimeout() 和 setInterval()。I/O callbacks: 这个阶段执行几乎所有的回调。但是不包括close事件定时器和setImmediate()的回调。idle, prepare: 这个阶段仅在内部使用可以不必理会。poll: 等待新的I/O事件node在一些特殊情况下会阻塞在这里。check: setImmediate()的回调会在这个阶段执行。close callbacks: 例如socket.on(close, ...)这种close事件的回调。 下面我们来按照代码第一次进入libuv引擎后的顺序来详细解说这些阶段 poll阶段 当个v8引擎将js代码解析后传入libuv引擎后循环首先进入poll阶段。poll阶段的执行逻辑如下 先查看poll queue中是否有事件有任务就按先进先出的顺序依次执行回调。 当queue为空时会检查是否有setImmediate()的callback如果有就进入check阶段执行这些callback。但同时也会检查是否有到期的timer如果有就把这些到期的timer的callback按照调用顺序放到timer queue中之后循环会进入timer阶段执行queue中的 callback。 这两者的顺序是不固定的收到代码运行的环境的影响。如果两者的queue都是空的那么loop会在poll阶段停留直到有一个i/o事件返回循环会进入i/o callback阶段并立即执行这个事件的callback。 值得注意的是poll阶段在执行poll queue中的回调时实际上不会无限的执行下去。有两种情况poll阶段会终止执行poll queue中的下一个回调1.所有回调执行完毕。2.执行数超过了node的限制。 check阶段 check阶段专门用来执行setImmediate()方法的回调当poll阶段进入空闲状态并且setImmediate queue中有callback时事件循环进入这个阶段。 close阶段 当一个socket连接或者一个handle被突然关闭时例如调用了socket.destroy()方法close事件会被发送到这个阶段执行回调。否则事件会用process.nextTick方法发送出去。 timer阶段 这个阶段以先进先出的方式执行所有到期的timer加入timer队列里的callback一个timer callback指得是一个通过setTimeout或者setInterval函数设置的回调函数。 I/O callback阶段 如上文所言这个阶段主要执行大部分I/O事件的回调包括一些为操作系统执行的回调。例如一个TCP连接生错误时系统需要执行回调来获得这个错误的报告。 4.process.nextTick,setTimeout与setImmediate的区别与使用场景 在node中有三个常用的用来推迟任务执行的方法process.nextTick,setTimeoutsetInterval与之相同与setImmediate 这三者间存在着一些非常不同的区别 process.nextTick() 尽管没有提及但是实际上node中存在着一个特殊的队列即nextTick queue。这个队列中的回调执行虽然没有被表示为一个阶段当时这些事件却会在每一个阶段执行完毕准备进入下一个阶段时优先执行。当事件循环准备进入下一个阶段之前会先检查nextTick queue中是否有任务如果有那么会先清空这个队列。与执行poll queue中的任务不同的是这个操作在队列清空前是不会停止的。这也就意味着错误的使用process.nextTick()方法会导致node进入一个死循环。。直到内存泄漏。 那么合适使用这个方法比较合适呢下面有一个例子 const server net.createServer(() {}).listen(8080);server.on(listening, () {});这个例子中当当listen方法被调用时除非端口被占用否则会立刻绑定在对应的端口上。这意味着此时这个端口可以立刻触发listening事件并执行其回调。然而这时候on(listening)还没有将callback设置好自然没有callback可以执行。为了避免出现这种情况node会在listen事件中使用process.nextTick()方法确保事件在回调函数绑定后被触发。 setTimeout()和setImmediate() 在三个方法中这两个方法最容易被弄混。实际上某些情况下这两个方法的表现也非常相似。然而实际上这两个方法的意义却大为不同。 setTimeout()方法是定义一个回调并且希望这个回调在我们所指定的时间间隔后第一时间去执行。注意这个“第一时间执行”这意味着受到操作系统和当前执行任务的诸多影响该回调并不会在我们预期的时间间隔后精准的执行。执行的时间存在一定的延迟和误差这是不可避免的。node会在可以执行timer回调的第一时间去执行你所设定的任务。 setImmediate()方法从意义上将是立刻执行的意思但是实际上它却是在一个固定的阶段才会执行回调即poll阶段之后。有趣的是这个名字的意义和之前提到过的process.nextTick()方法才是最匹配的。node的开发者们也清楚这两个方法的命名上存在一定的混淆他们表示不会把这两个方法的名字调换过来—因为有大量的node程序使用着这两个方法调换命名所带来的好处与它的影响相比不值一提。 setTimeout()和不设置时间间隔的setImmediate()表现上及其相似。猜猜下面这段代码的结果是什么 setTimeout(() {console.log(timeout); }, 0);setImmediate(() {console.log(immediate); });实际上答案是不一定。没错就连node的开发者都无法准确的判断这两者的顺序谁前谁后。这取决于这段代码的运行环境。运行环境中的各种复杂的情况会导致在同步队列里两个方法的顺序随机决定。但是在一种情况下可以准确判断两个方法回调的执行顺序那就是在一个I/O事件的回调中。下面这段代码的顺序永远是固定的 const fs require(fs);fs.readFile(__filename, () {setTimeout(() {console.log(timeout);}, 0);setImmediate(() {console.log(immediate);}); });答案永远是 immediate timeout因为在I/O事件的回调中setImmediate方法的回调永远在timer的回调前执行。 *尾声* javascrit的事件循环是这门语言中非常重要且基础的概念。清楚的了解了事件循环的执行顺序和每一个阶段的特点可以使我们对一段异步代码的执行顺序有一个清晰的认识从而减少代码运行的不确定性。合理的使用各种延迟事件的方法有助于代码更好的按照其优先级去执行。这篇文章期望用最易理解的方式和语言准确描述事件循环这个复杂过程但由于作者自己水平有限文章中难免出现疏漏。如果您发现了文章中的一些问题欢迎在留言中提出我会尽量回复这些评论把错误更正。 17、微任务和宏任务有什么区别 宏任务 (macro)task可以理解是每次执行栈执行的代码就是一个宏任务包括每次从事件队列中获取一个事件回调并放到执行栈中执行。 浏览器为了能够使得JS内部(macro)task与DOM任务能够有序的执行会在一个(macro)task执行结束后在下一个(macro)task 执行开始前对页面进行重新渲染流程如下 (macro)task-渲染-(macro)task-...宏任务包含 script(整体代码) setTimeout setInterval I/O UI交互事件 postMessage MessageChannel setImmediate(Node.js 环境)微任务 microtask,可以理解是在当前 task 执行结束后立即执行的任务。也就是说在当前task任务后下一个task之前在渲染之前。 所以它的响应速度相比setTimeoutsetTimeout是task会更快因为无需等渲染。也就是说在某一个macrotask执行完后就会将在它执行期间产生的所有microtask都执行完毕在渲染前。 微任务包含 Promise.then Object.observe MutaionObserver process.nextTick(Node.js 环境)18、什么是运算符 *被称为严格等式运算符当两个操作数具有相同的值而没有任何类型转换时该运算符返回true。* 19、异步解决方案有哪些 promise 回调 async await 20、Promise, 说说你的理解 一、Promise是什么 Promise是最早由社区提出和实现的一种解决异步编程的方案比其他传统的解决方案回调函数和事件更合理和更强大。 ES6 将其写进了语言标准统一了用法原生提供了Promise对象。 ES6 规定Promise对象是一个构造函数用来生成Promise实例。 二、Promise是为解决什么问题而产生的 promise是为解决异步处理回调金字塔问题而产生的 三、Promise的两个特点 1、Promise对象的状态不受外界影响 1pending 初始状态 2fulfilled 成功状态 3rejected 失败状态 Promise 有以上三种状态只有异步操作的结果可以决定当前是哪一种状态其他任何操作都无法改变这个状态 2、Promise的状态一旦改变就不会再变任何时候都可以得到这个结果状态不可以逆只能由 pending变成fulfilled或者由pending变成rejected 四、Promise的三个缺点 1无法取消Promise,一旦新建它就会立即执行无法中途取消 2如果不设置回调函数Promise内部抛出的错误不会反映到外部 3当处于pending状态时无法得知目前进展到哪一个阶段是刚刚开始还是即将完成 五、Promise在哪存放成功回调序列和失败回调序列 1onResolvedCallbacks 成功后要执行的回调序列 是一个数组 2onRejectedCallbacks 失败后要执行的回调序列 是一个数组 以上两个数组存放在Promise 创建实例时给Promise这个类传的函数中默认都是空数组。 每次实例then的时候 传入 onFulfilled 成功回调 onRejected 失败回调如果此时的状态是pending 则将onFulfilled和onRejected push到对应的成功回调序列数组和失败回调序列数组中如果此时的状态是fulfilled 则onFulfilled立即执行如果此时的状态是rejected则onRejected立即执行 上述序列中的回调函数执行的时候 是有顺序的即按照顺序依次执行 六、Promise的用法 1、Promise构造函数接受一个函数作为参数该函数的两个参数分别是resolve和reject。它们是两个函数由 JavaScript 引擎提供不用自己部署。 const promise new Promise(function(resolve, reject) {// ... some codeif (/* 异步操作成功 */){resolve(value);} else {reject(error);}});2、resolve函数的作用是将Promise对象的状态从“未完成”变为“成功”即从 pending 变为 resolved在异步操作成功时调用并将异步操作的结果作为参数传递出去reject函数的作用是将Promise对象的状态从“未完成”变为“失败”即从 pending 变为 rejected在异步操作失败时调用并将异步操作报出的错误作为参数传递出去。 3、Promise实例生成以后可以用then方法分别指定resolved状态和rejected状态的回调函数。 promise.then(function(value) {// success}, function(error) {// failure});then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用第二个回调函数是Promise对象的状态变为rejected时调用。其中第二个函数是可选的不一定要提供。这两个函数都接受Promise对象传出的值作为参数
http://www.w-s-a.com/news/747426/

相关文章:

  • 网站的seo方案鹰潭做网站的公司
  • 高级室内设计网站太原网站设计费用
  • 智信建设职业培训学校网站深圳做网站建设开发
  • 宣城市住房和城乡建设局网站网站界面设计专利
  • 免费个人网站建站申请如何做内网网站
  • 福州专业网站建设怎么做黄骅港怎么读
  • 望京 网站建设深圳发型网站建设
  • 电商网站的相同点医疗网站建设代理商
  • 网址导航网站有哪些易营宝智能建站
  • 私人定制哪个网站做的比较好免费网站使用
  • 嘉兴网站建设系统免费的seo优化
  • 购书网站开发的意义网站建设接单渠道
  • 网站站内搜索怎么做wordpress默认主题修改
  • 网站推广的表现方式交网站建设 域名计入什么科目
  • 龙岗南联网站建设公司江门市
  • 网站运行方案设计平台模式
  • 网站加入wordpress邳州城乡建设局网站
  • 两个网站如何使用一个虚拟主机东莞市网站seo内容优化
  • 湖南网站建设公司排名傲派电子商务网站建设总结
  • 网站建设求职要求互联网挣钱项目平台
  • 网站权重怎么做做黑彩网站能赚钱吗
  • 三台建设局网站网页设计购物网站建设
  • thinkphp大型网站开发市场调研公司招聘
  • 天宁区建设局网站七冶建设集团网站 江苏
  • 越南网站 后缀湘潭新思维网站
  • 环球旅行社网站建设规划书网钛cms做的网站
  • 软件资源网站wordpress不能识别语言
  • 东坑仿做网站西安私人网站
  • 公司想做个网站怎么办如何搭建视频网站
  • .net网站架设凯里网站建设哪家好