Script标签:向html插入js的办法


属性形容
asyncasync立刻下载脚本(仅实用于内部脚本)。
charsetcharset示意通过src属性指定的代码的字符集
deferdefer示意脚本能够提早到文档齐全被解析和显示之后再执行(仅实用于内部脚本)。
languagescript(已废除)示意编写代码应用的脚本语言。用 type 属性代替它。
srcURL规定内部脚本文件的 URL。
xml:spacepreserve规定是否保留代码中的空白。
typetext/xxxlanguage的替换属性,示意编写代码应用的脚本语言的内容类型,也称为MIME属性。

所有<script>元素会依照在页面呈现的先后顺序顺次被解析,没有 deferasync,浏览器会立刻加载并执行指定的脚本, 只有解析完后面的script元素的内容后,才会解析前面的代码。

应用<script>的两种形式
  1. 页面中嵌入script代码, 只需指定type属性
<script type="text/javascript">  // 外部不能呈现'</script>'字符串,如果必须呈现,必须应用本义标签‘\’  function sayHi() {    console.log('hihihi');    alert('<\/script>');  }</script>

蕴含在<script>元素内的代码会从上而下顺次解释,在解释器对<script>元素内的所有代码求值结束之前,页面中的其余内容都不会被浏览器加载或显示

  1. 蕴含内部js文件, src属性是必须的。
<script src="example.js"></script>// 带有src属性的元素不应该在标签之间蕴含额定的js代码,即便蕴含,只会下载并执行内部文件,外部代码也会被疏忽。

与嵌入式js代码一样, 在解析内部js文件时,页面的解决会临时进行。

扭转脚本行为的办法

1. defer: 立刻下载,提早执行

加载后续文档元素的过程将和 script.js 的加载并行进行(异步),然而 script.js 的执行要在所有元素解析实现之后。

会先与DOMContentLoaded事件执行。

提早脚本总会依照指定他们的程序执行。

<script defer="defer" src="example.js"></script>

2. async: 异步脚本

加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)。

肯定会在load事件之前执行,可能会在DOMContentLoaded事件之前或之后执行。

不能保障异步脚本依照他们在页面中呈现的程序执行。

<script async="async" src="example.js"></script>

区别:

  1. deferasync 在网络读取(下载)是一样的,都是异步的(相较于 HTML 解析)
  2. 它俩的差异在于脚本下载完之后何时执行,显然 defer 是最靠近咱们对于利用脚本加载和执行的要求的
  3. 对于 defer,它是依照加载程序执行脚本的
  4. async 则是一个乱序执行的主,反正对它来说脚本的加载和执行是紧紧挨着的,所以不论你申明的程序如何,只有它加载完了就会立即执行
  5. async 对于利用脚本的用途不大,因为它齐全不思考依赖(哪怕是最低级的程序执行),不过它对于那些能够不依赖任何脚本或不被任何脚本依赖的脚本来说却是十分适合的,最典型的例子:Google Analytics

根本类型和援用类型


根本类型:undefined、null、string、number、boolean、symbol

特点

  1. 根本类型的值是不可变得
// 任何办法都无奈扭转一个根本类型的值 let name = 'jay'; name.toUpperCase(); // 输入 'JAY' console.log(name); // 输入  'jay'
  1. 根本类型的比拟是值的比拟
// 只有在它们的值相等的时候它们才相等let a = 1;let b = true;console.log(a == b); //true// 用==比拟两个不同类型的变量时会进行一些类型转换。// 先会把true转换为数字1再和数字1进行比拟,后果就是true了
  1. 根本类型的变量是寄存在栈区的(栈区指内存里的栈内存)
援用类型:Object、Array、RegExp、Date、Function

也能够说是就是对象。对象是属性和办法的汇合,也就是说援用类型能够领有属性和办法,属性又能够蕴含根本类型和援用类型

特点

  1. 援用类型的值是可变的
// 咱们可为为援用类型增加属性和办法,也能够删除其属性和办法let person = { name: 'pig' };person.age = 22;person.sayName = () => console.log(person.name); person.sayName(); // 'pig'delete person.name;
  1. 援用类型的比拟是援用的比拟
let person1 = '{}';let person2 = '{}';console.log(person1 == person2); // 字符串值雷同,truelet person1 = {};let person2 = {};console.log(person1 == person2); // 两个对象的堆内存中的地址不同,false
  1. 援用类型的值是同时保留在栈内存和堆内存中的对象

javascript和其余语言不同,其不容许间接拜访内存中的地位,也就是说不能间接操作对象的内存空间。实际上,是操作对象的援用,所以援用类型的值是按援用拜访的。精确地说,援用类型的存储须要内存的栈区和堆区(堆区是指内存里的堆内存)共同完成,栈区内存保留变量标识符和指向堆内存中该对象的指针,也能够说是该对象在堆内存的地址。

闭包


闭包就是指有权拜访另一个函数作用域中的变量的函数。

官网解释:闭包是由函数以及创立该函数的词法环境组合而成。这个环境蕴含了这个闭包创立时所能拜访的所有局部变量。(词法作用域)

艰深解释:闭包的关键在于:内部函数调用之后其变量对象本应该被销毁,但闭包的存在使咱们依然能够拜访内部函数的变量对象。

当某个函数被掉用的时候,会创立一个执行环境及相应的作用域链。而后应用arguments和其余命名参数的值来初始化函数的流动对象。但在作用域链中,内部函数的流动对象始终处于第二位,内部函数的内部函数的流动对象处于第三位...直至作为作用域链起点的全局执行环境。

作用域链实质上是一个指向变量对象的指针列表,他只援用但不理论蕴含变量对象。

无论什么时候在函数中拜访一个变量时,就会从作用域链中搜寻具备雷同名字的变量,一般来讲,当函数执行结束,部分流动对象就会被销毁,内存中仅保留全副作用域的流动对象。然而,闭包不同。

创立闭包: 在一个函数外部创立另一个函数
function add() {  let a = 1;  let b = 3;  function closure() {     b++;     return a + b;  }  return closure;}// 闭包的作用域链蕴含着它本人的作用域,以及蕴含它的函数的作用域和全局作用域。
生命周期

通常,函数的作用域及其所有变量都会在函数执行完结后被销毁。然而,在创立了一个闭包当前,这个函数的作用域就会始终保留到闭包不存在为止。

当闭包中的函数closureadd中返回后,它的作用域链被初始化为蕴含add函数的流动对象和全局变量对象。这样closure就能够拜访在add中定义的所有变量。更重要的是,add函数在执行结束后,也不会销毁,因为closure函数的作用域链依然在援用这个流动对象。换句话说,当add返回后,其执行环境的作用域链被销毁,但它的流动对象依然在内存中,直至closure被销毁。

function add(x) {  function closure(y) {     return x + y;  }  return closure;}let add2 = add(2);let add5 = add(5);// add2 和 add5 共享雷同的函数定义,然而保留了不同的环境// 在add2的环境中,x为5。而在add5中,x则为10console.log(add2(3)); // 5console.log(add5(10)); // 15// 开释闭包的援用add2 = null;add5 = null;
闭包中的this对象
var name = 'window';var obj = {  name: 'object',  getName: () => {    return () => {      return this.name;    }  }}console.log(obj.getName()()); // window

obj.getName()()是在全局作用域中调用了匿名函数,this指向了window。

函数名与函数性能是宰割开的,不要认为函数在哪里,其外部的this就指向哪里。

window才是匿名函数性能执行的环境。

应用留神点

1)因为闭包会让蕴含函数中的变量都被保留在内存中,内存耗费很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决办法是,在退出函数之前,将不应用的局部变量全副删除。

2)闭包会在父函数内部,扭转父函数外部变量的值。所以,如果你把父函数当作对象(object)应用,把闭包当作它的专用办法(Public Method),把外部变量当作它的公有属性(private value),这时肯定要小心,不要轻易扭转父函数外部变量的值。

应用
  1. 模拟块级作用域
  2. 公有变量
  3. 模块模式
在循环中创立闭包:一个常见谬误
function show(i) {  console.log(i);}function showCallback(i) {  return () => {    show(i);  };}// 测试1【3,3,3】const testFunc1 = () => {  // var i;  for (var i = 0; i < 3; i++) {    setTimeout(() => show(i), 300);  }}// 测试2 【0,1,2】const testFunc2 = () => {  for (var i = 0; i < 3; i++) {    setTimeout(showCallback(i), 300);  }}// 测试3【0,1, 2】 闭包,立刻执行函数// 在闭包函数外部造成了部分作用域,每循环一次,造成一个本人的部分作用域const testFunc3 = () => {  for (var i = 0; i < 3; i++) {    (() => {       setTimeout(() => show(i), 300);    })(i);  }}// 测试4【0,1, 2】letconst testFunc4 = () => {  for (let i = 0; i < 3; i++) {    setTimeout(() => show(i), 300);  }}

setTimeout()函数回调属于异步工作,会呈现在宏工作队列中,被压到了工作队列的最初,在这段代码应该是for循环这个同步工作执行实现后才会轮到它

测试1谬误起因:赋值给 setTimeout 的是闭包。这些闭包是由他们的函数定义和在 testFunc1 作用域中捕捉的环境所组成的。这三个闭包在循环中被创立,但他们共享了同一个词法作用域,在这个作用域中存在一个变量i。这是因为变量i应用var进行申明,因为变量晋升,所以具备函数作用域。当onfocus的回调执行时,i的值被决定。因为循环在事件触发之前早已执行结束,变量对象i(被三个闭包所共享)曾经指向了i的最初一个值。

测试2正确起因: 所有的回调不再共享同一个环境, showCallback 函数为每一个回调创立一个新的词法环境。在这些环境中,i 指向数组中对应的下标。

测试4正确起因:JS中的for循环体比拟非凡,每次执行都是一个全新的独立的块作用域,用let申明的变量传入到 for循环体的作用域后,不会产生扭转,不受外界的影响。

作用域


javascript中变量或函数产生作用、而不会对外产生影响的关闭空间。内部不能够拜访外部变量或函数,但外部可能拜访内部。

ES5:

  1. 全局作用域:所有中央都能够拜访
  2. 函数作用域:只能在函数外部拜访

ES6:

  1. 减少了块级作用域(最近大括号的作用范畴),但仅限于let申明的变量
作用域链

规定:

  1. 全局变量,函数申明都是属于0级链,每个对象占一个地位
  2. 但凡看到函数就延长一个链进去,一级级开展
  3. 拜访首先看到以后函数,如果以后作用域链没有定义,往下级链中查看
  4. 如此往返,直到0级链,如果0级没有,则弹出谬误,这个变量没有定义
词法作用域

所谓词法(代码)作用域,就是代码在编写过程中体现进去的作用范畴,代码一旦写好了,没有运行之前(不必执行),作用范畴就曾经确定好了,这个就是所谓的词法作用域。

词法作用域的规定:

  1. 函数容许拜访函数内部的数据
  2. 整个代码构造中只有函数能力限定作用域
  3. 作用规定首先应用变量晋升规定剖析
  4. 如果以后作用规定外面有该名字,则不思考里面的里面的名字

词法作用域依据申明变量的地位来确定该变量可被拜访的地位。嵌套函数可获取申明于内部作用域的函数。

function init() {    var name = "Mozilla"; // name 是一个被 init 创立的局部变量    function displayName() { // displayName() 是外部函数,一个闭包        alert(name); // 应用了父函数中申明的变量    }    displayName();}init();
var let 区别
  1. var申明的变量,只有函数能力为它创立新的作用域

let反对块级作用域,花括号就能为它创立新的作用域

  1. 雷同作用域,var能够重复申明雷同标识符的变量,而let是不容许的;
  2. let申明的变量禁止在申明前拜访
// 全局变量var i = 0 ;// 定义内部函数function outer(){    // 拜访全局变量    console.log(i); // 0      function inner1(){        console.log(i); // 0    }    function inner2(){        console.log(i); // undefined        var i = 1;        console.log(i); // 1    }      inner1();    inner2();    console.log(i); // 0}

变量晋升


在Javascript中,函数及变量的申明都将被晋升到函数的最顶部,晋升的仅仅是变量的申明,变量的赋值并不会被晋升。咱们须要留神的是,函数的申明与变量的申明是不一样的。函数的函数体也会被一起晋升。
函数表达式和变量表达式只是其申明被晋升,函数申明是函数的申明和实现都被晋升。

function foo() {    console.log("global foo");  }  function bar() {     console.log("global bar");  }  //定义全局变量  var v = "global var";  function hoistMe() {   // var bar; 被晋升到顶部,并未实现   // var v;   console.log(typeof foo); //function     console.log(typeof bar); //undefined     console.log(v); //undefined       // 函数外面定义了同名的函数和变量,无论在函数的任何地位定义这些函数和和变量,它们都将被晋升到函数的最顶部。     foo(); //local foo     bar(); //报错,TypeError "bar is not a function"    //函数申明,变量foo以及其实现被晋升到hoistMe函数顶部     function foo() {       alert("local foo");     }     //函数表达式,仅变量bar被晋升到函数顶部,实现没有被晋升     var bar = function() {         alert("local bar");     };     //定义局部变量     var v = "local";  }  

let 变量晋升

console.log(a); // Uncaught ReferenceError: a is not definedlet a = "I am a";let b = "I am outside B";if(true){    console.log(b); // Uncaught ReferenceError: b is not defined    let b = " I am inside B";}

如果b没有变量晋升,执行到console.log时应该是输入全局作用域中的b,而不是呈现谬误。

咱们能够推知,这里的确呈现了变量晋升,而咱们不可能拜访的起因事实上是因为let的死区设计:以后作用域顶部到该变量申明地位两头的局部,都是该let变量的死区,在死区中,禁止拜访该变量。由此,咱们给出论断,let申明的变量存在变量晋升, 然而因为死区咱们无奈在申明前拜访这个变量。

this 指向问题


this 就是一个指针,指向咱们调用函数的对象。

执行上下文: 是语言标准中的一个概念,用艰深的话讲,大抵等同于函数的执行“环境”。具体的有:变量作用域(和 作用域链条,闭包外面来自内部作用域的变量),函数参数,以及 this 对象的值。

找出 this 的指向

this 的值并不是由函数定义放在哪个对象外面决定,而是函数执行时由谁来唤起决定。

var name = "Jay Global";var person = {    name: 'Jay Person',    details: {        name: 'Jay Details',        print: function() {            return this.name;        }    },    print: function() {        return this.name;    }};console.log(person.details.print());  // 【details对象调用的print】Jay Detailsconsole.log(person.print());          // 【person对象调用的print】Jay Personvar name1 = person.print;var name2 = person.details;console.log(name1()); // 【name1后面没有调用对象,所以是window】Jay Globalconsole.log(name2.print()) // 【name2对象调用的print】Jay Details
this和箭头函数

箭头函数按词法作用域来绑定它的上下文,所以 this 实际上会援用到原来的上下文。

箭头函数放弃它以后执行上下文的词法作用域不变,而一般函数则不会。换句话说,箭头函数从蕴含它的词法作用域中继承到了 this 的值。

匿名函数,它不会作为某个对象的办法被调用, 因而,this 关键词指向了全局 window 对象。

var object = {    data: [1,2,3],    dataDouble: [1,2,3],    double: function() {        console.log(this); // object        return this.data.map(function(item) { // this是以后object,object调用的double            console.log(this);   // 传给map()的那个匿名函数没有被任一对象调用,所以是window            return item * 2;        });    },    doubleArrow: function() {        console.log(this); // object        return this.dataDouble.map(item => { // this是以后object,object调用的doubleArrow            console.log(this);      // doubleArrow是object调用的,这就是上下文,所以是window            return item * 2;        });    }};object.double();object.doubleArrow();
明确设置执行上下文

在 JavaScript 中通过应用内置的个性开发者就能够间接操作执行上下文了。这些个性包含:

  • bind():不须要执行函数就能够将 this 的值精确设置到你抉择的一个对象上。通过逗号隔开传递多个参数。 设置好 this 关键词后不会立即执行函数。
  • apply():将 this 的值精确设置到你抉择的一个对象上。apply(thisObj, argArray)接管两个参数,thisObj是函数运行的作用域(this),argArray是参数数组,数组的每一项是你心愿传递给函数的参数。如果没有提供argArray和thisObj任何一个参数,那么Global对象将用作thisObj。最初,会立即执行函数。
  • call():将 this 的值精确设置到你抉择的一个对象上。而后像bind 一样通过逗号分隔传递多个参数给函数。语法:call(thisObj,arg1,arg2,..., argn);,如果没有提供thisObj参数,那么Global对象被用于thisObj。最初,会立即执行函数。

this 和 bind

var bobObj = {    name: "Bob"};function print() {    return this.name;}var printNameBob = print.bind(bobObj);console.log(printNameBob()); // Bob

this 和 call

function add(a, b) {     return a + b; }function sum() {    return Array.prototype.reduce.call(arguments, add);}console.log(sum(1,2,3,4)); // 10

this 和 apply

apply 就是承受数组版本的call。

Math.min(1,2,3,4); // 返回 1Math.min([1,2,3,4]); // 返回 NaN。只承受数字Math.min.apply(null, [1,2,3,4]); // 返回 1function Person(name, age){    this.name = name;    this.age = age;  }  function Student(name, age, grade) {    Person.apply(this, arguments);  //Person.call(this, name, age);  this.grade = grade;  }var student = new Student("sansan", 21, "一年级");   console.log("student:", student); // {name: 'sansan'; age: '21', grade: '一年级'}

如果你的参数原本就存在一个数组中,那天然就用 apply,如果参数比拟散乱相互之间没什么关联,就用 call。

对象属性类型


数据属性

数据属性蕴含一个数据值的地位,在这个地位能够读取和写入值,数据属性有4个形容其行为的个性:

  • Configurable: 示意是否通过delete删除属性从而从新定义属性,是否批改属性的个性,或者是否把属性批改为拜访器属性。默认值是true
  • Enumerable: 示意是否通过for-in循环返回属性。默认值是true
  • Writable: 表述是否批改属性。默认值是true
  • Value: 蕴含这个属性的数据值。默认值是true
拜访器属性

函数式编程


函数式编程是一种编程范式,是一种构建计算机程序构造和元素的格调,它把计算看作是对数学函数的评估,防止了状态的变动和数据的可变。

纯函数

纯函数是稳固的、统一的和可预测的。给定雷同的参数,纯函数总是返回雷同的后果。

纯函数有一个十分严格的定义:

  • 如果给定雷同的参数,则返回雷同的后果(也称为确定性)。
  • 它不会引起任何副作用。

纯函数个性

  1. 如果给定雷同的参数,则失去雷同的后果

咱们想要实现一个计算圆的面积的函数。

不是纯函数会这样做:

  let PI = 3.14;      const calculateArea = (radius) => radius * radius * PI;   // 它应用了一个没有作为参数传递给函数的全局对象  calculateArea(10); // returns 314.0

纯函数:

 let PI = 3.14;     const calculateArea = (radius, pi) => radius * radius * pi;  // 当初把 PI 的值作为参数传递给函数,这样就没有内部对象引入。 calculateArea(10, PI); // returns 314.0
  1. 无显著副作用

纯函数不会引起任何可察看到的副作用。可见副作用的例子包含批改全局对象或通过援用传递的参数。
当初,实现一个函数,接管一个整数并返对该整数进行加1操作且返回。

let counter = 1;function increaseCounter(value) {        counter = value + 1;   }    increaseCounter(counter);   console.log(counter); // 2

该非纯函数接管该值并重新分配counter,使其值减少1

函数式编程不激励可变性(批改全局对象)。

 let counter = 1; const increaseCounter = (value) => value + 1;   // 函数返回递增的值,而不扭转变量的值 increaseCounter(counter); // 2     console.log(counter); // 1

纯函数的益处

纯函数代码必定更容易测试,不须要 mock 任何货色,因而,咱们能够应用不同的上下文对纯函数进行单元测试:

一个简略的例子是接管一组数字,并对每个数进行加 1

let list = [1, 2, 3, 4, 5];    const incrementNumbers = (list) => list.map(number => number + 1);incrementNumbers(list); // [2, 3, 4, 5, 6]

对于输出[1,2,3,4,5],预期输入是[2,3,4,5,6]

援用透明性

实现一个square 函数:

const square = (n) => n * n;square(2); // 4 将2作为square函数的参数传递始终会返回4

能够把square(2)换成4,咱们的函数就是援用通明的。

如果一个函数对于雷同的输出始终产生雷同的后果,那么它能够看作通明的。

函数也能够被看作成值并用作数据应用。

  • 从常量和变量中援用它。
  • 将其作为参数传递给其余函数。
  • 作为其余函数的后果返回它。

其思维是将函数视为值,并将函数作为数据传递。通过这种形式,咱们能够组合不同的函数来创立具备新行为的新函数。

如果咱们有一个函数,它对两个值求和,而后将值加倍,如下所示:

 const doubleSum = (a, b) => (a + b) * 2;

对应两个值求差,而后将值加倍:

  const doubleSubtraction = (a, b) => (a - b) * 2

这些函数具备类似的逻辑,但区别在于运算符的性能。如果咱们能够将函数视为值并将它们作为参数传递,咱们能够构建一个接管运算符函数并在函数外部应用它的函数。

 const sum = (a, b) => a + b;     const subtraction = (a, b) => a - b;     const doubleOperator = (f, a, b) => f(a, b) * 2;     doubleOperator(sum, 3, 1); // 8     doubleOperator(subtraction, 3, 1); // 4

Promise


Promise 必须为以下三种状态之一:期待态(Pending)、执行态(Fulfilled)和回绝态(Rejected)。一旦Promise 被 resolve 或 reject,不能再迁徙至其余任何状态(即状态 immutable)。

根本过程:

  1. 初始化 Promise 状态(pending)
  2. 执行 then(..) 注册回调解决数组(then 办法可被同一个 promise 调用屡次)
  3. 立刻执行 Promise 中传入的 fn 函数,将Promise 外部 resolve、reject 函数作为参数传递给 fn ,按事件机制机会解决
  4. Promise中要保障,then办法传入的参数 onFulfilled 和 onRejected,必须在then办法被调用的那一轮事件循环之后的新执行栈中执行。

真正的链式Promise是指在以后promise达到fulfilled状态后,即开始进行下一个promise.

跨域


不同地址,不同端口,不同级别,不同协定都会形成跨域。

跨域计划

window.postMessage

window.postMessage是html5的性能,是客户端和客户端间接的数据传递,既能够跨域传递,也能够同域传递。

栗子:如果有一个页面,页面中拿到局部用户信息,点击进入另外一个页面,另外的页面默认是取不到用户信息的,你能够通过window.postMessage把局部用户信息传到这个页面中。(须要思考安全性等方面。)

发送信息

//弹出一个新窗口var domain = 'http://haorooms.com';var myPopup = window.open(`${domain}/windowPostMessageListener.html`,'myWindow');//周期性的发送音讯setTimeout(function(){    var message = {name:"站点",sex:"男"}; //你在这里也能够传递一些数据,obj等    console.log('传递的数据是  ' + message);    myPopup.postMessage(message, domain);},1000);

要提早一下,咱们个别用计时器setTimeout提早再发用。

承受的页面

//监听音讯反馈window.addEventListener('message',function(event) {    if(event.origin !== 'http://haorooms.com') return;    //这个判断一下是不是我这个域名跳转过来的    console.log('received response: ', event.data);}, false);

如下图,承受页面失去数据

如果是应用iframe,代码应该这样写:

//捕捉iframevar domain = 'http://haorooms.com';var iframe = document.getElementById('myIFrame').contentWindow;//发送音讯setTimeout(function(){     var message = {name:"站点",sex:"男"};    console.log('传递的数据是:  ' + message);    iframe.postMessage(message, domain); },1000);

承受数据

//响应事件window.addEventListener('message',function(event) {    if(event.origin !== 'http://haorooms.com') return;    console.log('message received:  ' + event.data,event);    event.source.postMessage('holla back youngin!',event.origin);},false);

下面的代码片段是往音讯源反馈信息,确认音讯曾经收到。上面是几个比拟重要的事件属性:

**source – 音讯源,音讯的发送窗口/iframe。
origin – 音讯源的URI(可能蕴含协定、域名和端口),用来验证数据源。
data – 发送方发送给接管方的数据。**

CORS跨域资源共享

CORS跨域和jsonp跨域的劣势:

CORS与JSONP相比,无疑更为先进、不便和牢靠。

1、 JSONP只能实现GET申请,而CORS反对所有类型的HTTP申请。

2、 应用CORS,开发者能够应用一般的XMLHttpRequest发动申请和取得数据,比起JSONP有更好的错误处理。

3、 JSONP次要被老的浏览器反对,它们往往不反对CORS,而绝大多数古代浏览器都曾经反对了CORS

CORS的应用

CORS要前后端同时做配置。

1、首先咱们来看前端。

纯js的ajax申请。

<script type="text/javascript">    var xhr = new XMLHttpRequest();     // ie6以下用new ActiveXObject("Microsoft.XMLHTTP");能够做能力判断。    xhr.open("GET", "/haorooms", true);    xhr.send();</script>

以上的haorooms是相对路径,如果咱们要应用CORS,相干Ajax代码可能如下所示:

<script type="text/javascript">    var xhr = new XMLHttpRequest();    //ie6以下用new ActiveXObject("Microsoft.XMLHTTP");能够做能力判断。    xhr.open("GET", "http://www.haorooms.com/CORS", true);    xhr.send();</script>

当然,你也能够用jquery的ajax进行。

2、后端或者服务器端的配置

上面咱们次要介绍Apache和PHP里的设置办法。

Apache:Apache须要应用mod_headers模块来激活HTTP头的设置,它默认是激活的。你只须要在Apache配置文件的 < Directory >, < Location>, < Files >或< VirtualHost>的配置里退出以下内容即可:

Header set Access-Control-Allow-Origin *  

PHP:只须要应用如下的代码设置即可。

<?php   header("Access-Control-Allow-Origin:*");  

以上的配置的含意是容许任何域发动的申请都能够获取以后服务器的数据。当然,这样有很大的危险性,歹意站点可能通过XSS攻打咱们的服务器。所以咱们应该尽量有针对性的对限度平安的起源,例如上面的设置使得只有www.haorooms.com这个域能力跨域拜访服务器的API。

 Access-Control-Allow-Origin: http://www.haorooms.com 
通过jsonp跨域

jsonp跨域也须要前后端配合应用。个别后端设置callback ,前端给后盾接口中传一个callback 就能够。

例如前端代码:

<script type="text/javascript">    function dosomething(jsondata){        //解决取得的json数据    }</script><script src="http://haorooms.com/data.php?callback=dosomething"></script>

后盾代码:

<?php  $callback = $_GET['callback'];//失去回调函数名  $data = array('a','b','c');//要返回的数据  echo $callback.'('.json_encode($data).')';//输入?>

如果你用ajax形式进行jsonp跨域:

$.ajax({      type : "get",      url : "跨域地址",      dataType : "jsonp",//数据类型为jsonp      jsonp: "callback",    //服务端用于接管callback调用的function名的参数【后盾承受什么参数,咱们就传什么参数】    success : function(data){          //后果解决    },      error:function(data){            console.log(data);    }  });
通过批改document.domain来跨子域

咱们只须要在跨域的两个页面中设置document.domain就能够了。批改document.domain的办法只实用于不同子域的框架间的交互。

栗子:

1.在页面 http:// www.haorooms.com/a.html 中设置document.domain

<iframe id = "iframe" src="http://haorooms.com/b.html" onload = "test()"></iframe><script type="text/javascript">    document.domain = 'haorooms.com';//设置成主域    function test(){       alert(document.getElementById('iframe').contentWindow);       //contentWindow 可获得子窗口的 window 对象    }</script>

2、在页面http:// haorooms.com/b.html 中设置document.domain

<script type="text/javascript">    document.domain = 'haorooms.com';    //在iframe载入这个页面也设置document.domain,使之与主页面的document.domain雷同</script>
应用window.name来进行跨域

原理:

window对象有个name属性,该属性有个特色:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是长久存在一个窗口载入过的所有页面中的。

办法:

如果有三个页面。

a.com/app.html:利用页面。a.com/proxy.html:代理文件,个别是一个没有任何内容的html文件,须要和利用页面在同一域下。b.com/data.html:利用页面须要获取数据的页面,可称为数据页面。

1、在利用页面(a.com/app.html)中创立一个iframe,把其src指向数据页面(b.com/data.html)。

数据页面会把数据附加到这个iframe的window.name上,data.html代码如下:

<script type="text/javascript">   window.name = 'I was there!';       // 这里是要传输的数据,大小个别为2M,IE和firefox下能够大至32M左右   // 数据格式能够自定义,如json、字符串</script>

2、在利用页面(a.com/app.html)中监听iframe的onload事件,在此事件中设置这个iframe的src指向本地区的代理文件(代理文件和利用页面在同一域下,所以能够互相通信)。

app.html局部代码如下:

<script type="text/javascript">    var state = 0,     iframe = document.createElement('iframe'),    loadfn = function() {        if (state === 1) {            var data = iframe.contentWindow.name; // 读取数据            alert(data);        } else if (state === 0) {            state = 1;            iframe.contentWindow.location = "http://a.com/proxy.html";                // 设置的代理文件        }      };    iframe.src = 'http://b.com/data.html';    if (iframe.attachEvent) {        iframe.attachEvent('onload', loadfn);    } else {        iframe.onload  = loadfn;    }    document.body.appendChild(iframe);</script>

3、获取数据当前销毁这个iframe,开释内存;这也保障了平安(不被其余域frame js拜访)。

<script type="text/javascript">    iframe.contentWindow.document.write('');    iframe.contentWindow.close();    document.body.removeChild(iframe);</script>
webpack解决跨域问题

webpack也能够解决前端跨域问题,只须要装置webpack 的http-proxy-middleware模块就能够

npm install http-proxy-middleware --save-dev

配置如下:

module.exports = {  devtool: 'cheap-module-source-map',  entry: './app/js/index.js'  output: {  path: path.resolve(__dirname, 'dev'),    // 所有输入文件的指标门路    filename: 'js/bundle.js',    publicPath: '/',    chunkFilename: '[name].chunk.js'  },  devServer: {    contentBase: path.resolve(__dirname, 'dev'),    publicPath: '/',    historyApiFallback: true,    proxy: {      // 申请到 '/device' 下的申请都会被代理到target:http://debug.haorooms.com中      '/device/*': {        target: 'http://debug.haorooms.com',        secure: false, // 承受运行在https上的服务        changeOrigin: true      }    }  }}

应用如下:

fetch('/device/space').then(res => {  // 被代理到 http://debug.haorooms.com/device/space  return res.json();});fetch('device/space').then(res => {  // http://localhost:8080/device/space 拜访本地服务  return res.json();});// 注:应用的url 必须以/开始 否则不会代理到指定地址
cookie跨域

业务场景

1、百度www域名上面登录了,发现yun域名上面也自然而然登录了。

2、淘宝登录了,发现天猫也登录了,淘宝和天猫是齐全不一样的2个域名。

栗子

  1. 同一个主域上面的跨域问题,相似www.baidu 和yun.baidu

cookie属性:
path

cookie 个别都是因为用户拜访页面而被创立的,然而并不是只有在创立 cookie 的页面才能够拜访这个cookie。在默认状况下,出于平安方面的思考,只有与创立 cookie 的页面处于同一个目录或在创立cookie页面的子目录下的网页才能够拜访。那么此时如果心愿其父级或者整个网页都可能应用cookie,就须要进行门路的设置。

path示意cookie所在的目录,haorooms.com默认为/,就是根目录。 在同一个服务器上有目录如下:

/post/,/post/id/,/tag/,/tag/haorooms/,/tag/id/

现假如一个

cookie1的path为/tag/,

cookie2的path为/tag/id/,

那么tag下的所有页面都能够拜访到cookie1,而/tag/和/tag/haorooms/的子页面不能拜访cookie2。 这是因为cookie2能让其path门路下的页面拜访。

让这个设置的cookie 能被其余目录或者父级的目录拜访的办法:

document.cookie = "name = value; path=/";

domain

domain示意的是cookie所在的域,默认为申请的地址,

如网址为 http://www.haorooms.com/post/... ,那么domain默认为www.haorooms.com。

而跨域拜访,

如域A为love.haorooms.com,域B为resource.haorooms.com,

那么在域A生产一个令域A和域B都能拜访的cookie就要将该cookie的domain设置为.haorooms.com;

如果要在域A生产一个令域A不能拜访而域B能拜访的cookie就要将该cookie的domain设置为resource.haorooms.com。

这样,咱们就晓得为什么www.百度 和yun.baidu共享cookie,咱们只须要设置domain为.baidu.com就能够了【注:点好是必须的】

  1. 天猫和淘宝是如何共享cookie的?

cookie跨域解决方案个别有如下几种:

1、nginx反向代理

反向代理(Reverse Proxy)形式是指以代理服务器来承受Internet上的连贯申请,而后将申请转发给外部网络上的服务器;并将从服务器上失去的后果返回给Internet上申请连贯的客户端,此时代理服务器对外就体现为一个服务器。

反向代理服务器对于客户端而言它就像是原始服务器,并且客户端不须要进行任何特地的设置。客户端向反向代理 的命名空间(name-space)中的内容发送一般申请,接着反向代理将判断向何处(原始服务器)转交申请,并将取得的内容返回给客户端,就像这些内容 本来就是它本人的一样。

nginx配置如下:

upstream web1{     server  127.0.0.1:8089  max_fails=0 weight=1;}upstream web2 {     server 127.0.0.1:8080    max_fails=0 weight=1;}    location /web1 {        proxy_pass http://web1;        proxy_set_header Host  127.0.0.1;        proxy_set_header   X-Real-IP        $remote_addr;        proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;        proxy_set_header Cookie $http_cookie;        log_subrequest on;    }    location /web2 {        proxy_pass http://web2;        proxy_set_header Host  127.0.0.1;        proxy_set_header   X-Real-IP        $remote_addr;        proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;        proxy_set_header Cookie $http_cookie;        log_subrequest on;    }

3、nodejs superagent

package.json中的模块依赖:

调用superagent api申请:

其实实质也是jsonp的形式。

同一域下,不同工程下的cookie携带问题

cookie跨域拜访之后,能够胜利的写入本地区。本地的前端工程在申请后端工程时,有很多是ajax申请,ajax默认不反对携带cookie,所以当初有以下两种计划:

(1). 应用jsonp格局发送(2). ajax申请中加上字段 xhrFields: {withCredentials: true},这样能够携带上cookie

服务器须要配置:

Access-Control-Allow-Credentials: true
localStorage跨域

模块化


AMD、CMD 和 CommonJS 的模块化开发

AMD/CMD/CommonJs都是JS模块化开发的规范,目前对应的实现是RequireJS,SeaJs, nodeJs;

CommonJS:服务端js

CommonJS 是以在浏览器环境之外构建 javaScript 生态系统为指标而产生的写一套标准,次要是为了解决 javaScript 的作用域问题而定义的模块模式,能够使每个模块它本身的命名空间中执行。

实现办法:模块必须通过 module.exports 导出对外的变量或者接口,通过 require() 来导入其余模块的输入到以后模块的作用域中;

次要针对服务端(同步加载文件)和桌面环境中,node.js 遵循的是 CommonJS 的标准;
CommonJS 加载模块是同步的,所以只有加载实现能力执行前面的操作.

**require()用来引入内部模块;
exports对象用于导出以后模块的办法或变量,惟一的导进口;
module对象就代表模块自身。**

// 定义一个module.js文件var A = () => console.log('我是定义的模块');// 1.第一种返回形式module.exports = A; // 2.第二种返回形式 module.exports.test = A// 3.第三种返回形式 exports.test = A;// 定义一个test.js文件【这两个文件在同一个目录下】var module = require("./module");//调用这个模块,不同的返回形式用不同的形式调用// 1.第一种调用形式module();// 2.第二种调用形式 module.test();// 3.第三种调用形式 module.test();// 执行文件node test.js

AMD: 异步模块定义【浏览器端js】

AMD 是 Asynchronous Module Definition 的缩写,意思是异步模块定义;采纳的是异步的形式进行模块的加载,在加载模块的时候不影响后边语句的运行。次要是为前端 js 的体现指定的一套标准。

实现办法:通过define办法去定义模块,通过require办法去加载模块。

define(id?,dependencies?,factory): 它要在申明模块的时候制订所有的依赖(dep),并且还要当做形参传到factory中。没什么依赖,就定义简略的模块(或者叫独立的模块)

require([modules], callback):第一个参数[modules],是需加载的模块名数组;第二个参数callback,是模块加载胜利之后的回调函数

次要针对浏览器js,requireJs遵循的是 AMD 的标准;

// module1.js文件, 定义独立的模块define({    methodA: () => console.log('我是module1的methodA');    methodB: () => console.log('我是module1的methodB');});// module2.js文件, 另一种定义独立模块的形式define(() => {    return {        methodA: () => console.log('我是module2的methodA');        methodB: () => console.log('我是module2的methodB');    };});// module3.js文件, 定义非独立的模块(这个模块依赖其余模块)define(['module1', 'module2'], (m1, m2) => {    return {        methodC: () => {            m1.methodA();            m2.methodB();        }    };});//定义一个main.js,去加载这些个模块require(['module3'], (m3) => {    m3.methodC();});// 为防止造成网页失去响应,解决办法有两个,一个是把它放在网页底部加载,另一个是写成上面这样:<script src="js/require.js" defer async="true" ></script>// async属性表明这个文件须要异步加载,防止网页失去响应。// IE不反对这个属性,只反对defer,所以把defer也写上。// data-main属性: 指定网页程序的主模块<script data-main="main" src="js/require.js"></script>// 控制台输入后果我是module1的methodA我是module2的methodB

CMD: 通用模块定义【浏览器端js】

  CMD 是 Common Module Definition 的缩写,通过异步的形式进行模块的加载的,在加载的时候会把模块变为字符串解析一遍才晓得依赖了哪个模块;

次要针对浏览器端(异步加载文件),按需加载文件。对应的实现是seajs

AMD和CMD的区别:

  1. 对于依赖的模块,AMD 是提前执行,CMD 是提早执行。不过 RequireJS 从 2.0 开始,也改成能够提早执行(依据写法不同,解决形式不同)。CMD 推崇 as lazy as possible(尽可能的懒加载,也称为提早加载,即在须要的时候才加载)。

  2. CMD 推崇依赖就近,AMD 推崇依赖前置。

// CMDdefine(function(require, exports, module) {    var a = require('./a');    a.doSomething();    // ...    var b = require('./b');   // 依赖能够就近书写    b.doSomething();    // ... })// AMDdefine(['./a', './b'], function(a, b) { // 依赖必须一开始就写好    a.doSomething();    // ...    b.doSomething();    //...}) 
import和require

import和require都是被模块化应用

  • require是CommonJs的语法(AMD标准引入形式),CommonJs的模块是对象。
  • import是es6的一个语法规范(浏览器不反对,实质是应用node中的babel将es6转码为es5再执行,import会被转码为require),es6模块不是对象
  • require是运行时加载整个模块(即模块中所有办法),生成一个对象,再从对象上读取它的办法(只有运行时能力失去这个对象,不能在编译时做到动态化),实践上能够用在代码的任何中央。
  • import是编译时调用,确定模块的依赖关系,输出变量(es6模块不是对象,而是通过export命令指定输入代码,再通过import输出,只加载import中导的办法,其余办法不加载),import具备晋升成果,会晋升到模块的头部(编译时执行)

export和import能够位于模块中的任何地位,然而必须是在模块顶层,如果在其余作用域内,会报错
es6这样的设计能够进步编译器效率,但没法实现运行时加载

  • require是赋值过程,把require的后果(对象,数字,函数等),默认是export的一个对象,赋给某个变量(复制或浅拷贝)
  • import是解构过程(须要谁,加载谁)
// require/exports(仅有上面的三种简略写法)const a = require('a') //真正被require进去的是来自module.exports指向的内存块内容exports.a = a //exports 只是 module.exports的援用,辅助module.exports操作内存中的数据module.exports = a// import/exportimport a from 'a'import { default as a  } from 'a'import  *  as a  from 'a'import { fun1,fun2 } from 'a'export default aexport const a=1export functon a{ }export { fun1,fun2 }
Require.js

require.js的诞生,就是为了解决这两个问题:

(1)实现js文件的异步加载,防止网页失去响应;

(2)治理模块之间的依赖性,便于代码的编写和保护。

加载js文件

加载require.js,也可能造成网页失去响应。解决办法有两个,一个是把它放在网页底部加载,另一个是写成上面这样:

<script src="js/require.js" defer async="true" ></script>

async属性表明这个文件须要异步加载,防止网页失去响应。

IE不反对async,只反对defer,所以把defer也写上。

require.config()

在用require()加载之前,要先用require.config()办法,定义它们的一些特色

require.config({  baseUrl: "js/lib",  paths: {    "jquery": "jquery.min",    "underscore": "underscore.min",    "backbone": "backbone.min"  },  shim: {    'underscore':{      exports: '_'    },    'backbone': {      deps: ['underscore', 'jquery'],      exports: 'Backbone'    }  }});

URL


从 URL 输出到页面展示大抵流程概述:

  1. 在浏览器输出 URL;
  2. 浏览器查找域名对应的 IP 地址;
  3. 浏览器依据 IP 地址与服务器建立联系;
  4. 浏览器与服务器通信:浏览器申请,服务器解决申请并出现页面。
具体流程概述

第一步,在浏览器输出 URL

在地址栏输出相应的 URL 。

第二步,浏览器查找域名对应的 IP 地址

第一步中,咱们曾经输出了相应的 URL,但浏览器自身并不能辨认 URL 是什么,因而从咱们输出 URL 开始,浏览器就要进行域名解析来找到对应 IP——DNS 解析是浏览器的理论寻址形式:

  • 查找浏览器缓存——近期浏览过的网站,浏览器会缓存 DNS 记录一段时间 (如果没有则往下) ;
  • 查找零碎缓存——从 C 盘的 hosts 文件查找是否有存储的 DNS 信息,查找是否有指标域名和对应的 IP 地址 (如果没有则往下);
  • 查找路由器缓存 (如果没有则往下);
  • 查找 ISP DNS 缓存——从网络服务商(比方电信)的 DNS 缓存信息中查找(如果没有则往下);
  • 经由以上形式都没找到对应 IP 的话,就会向根域名服务器查找指标 URL 对应的 IP,根域名服务器会向下级服务器转达申请,层层下发,直到找到对应的 IP 为止。

第三步,浏览器依据 IP 地址与服务器建立联系

依据IP建设TCP连贯(三次握手)

第 2 步中,浏览器通过 IP 寻址找到了对应的服务器,浏览器就将用户发动的 HTTP 申请发送给服务器。

服务器开始解决用户申请:

  • 每台服务器上都会装置解决申请的利用——Web Server;
  • 常见的 Web Server 产品有:Apache 、Nginx 、IIS 和 lighttpd 等;
  • Web Server 能够了解为一个管理者,它不做具体的申请解决,而是会联合配置文件,把不同用户发来的申请委托给服务器上专门解决相应申请的程序(服务器上的相应程序开始解决申请的这一部分,艰深说就是理论后盾解决的工作):

后盾开发当初有很多框架,但大部分都是依照 MVC(Model View Controller)设计模式搭建的,它将服务器上的应用程序分成 3 个核心部件且别离解决本人的工作,实现输出、解决、输入的拆散:

  • 模型(Model)

模型,是将理论开发过程中的业务规定和所波及的数据格式进行模型化;
利用于模型的代码只需写一次就能够被多个视图重用;
在 MVC 三个部件中,模型领有最多的解决工作;
一个模型能为多个视图提供数据。

  • 视图(View)

视图是用户看到并与之交互的界面。

  • 控制器(Controller)

作用:承受用户的输出并调用模型(M)和视图(V)去实现用户的需要;
位置:控制器也是处于一个管理者的位置——从视图(V)接管申请并决定调用哪一个模型构件(M)来解决申请,而后再确定用哪个视图(V)来显示模型(M)解决返回的数据。

总而言之:

首先,控制器(C)接管用户的申请,并决定应该调用哪个模型(M)来进行解决;
而后,模型(M)用业务逻辑来解决用户的申请并返回数据;
最初,控制器(C)再用相应的视图(V)来格式化模型(M),进而返回 HTML 字符串给浏览器。

第四步,浏览器与服务器通信

在上边的第 3 步中,服务器返回了 HTML 字符串给浏览器,此时,浏览器将会对其进行解析、渲染并最终绘制网页:

  1. 加载
  • 浏览器对一个 HTML 页面的加载程序是从上而下的;
  • 浏览器在加载的过程中,同时进行解析、渲染解决;
  • 在这个过程中,遇到 link 标签、image 标签、script 标签时,浏览器会再次向服务器发送申请以获取相应的 CSS 文件、图片资源、JS 文件,并执行 JS 代码,同步进行加载、解析。
  1. 解析、渲染
  • 解析的过程,其实就是生成“”(Document Object Model 文档对象模型);
  • DOM 树是由 DOM 元素及属性节点组成,并且加上 CSS 解析的款式对象和 JS 解析后的动作实现;
  • 渲染:就是将 DOM 树进行可视化示意。
  1. 绘制网页
  • 浏览器通过渲染,将 DOM 树可视化,失去渲染树;
  • 构建渲染树使页面以正确的程序绘制进去,浏览器遵循肯定的渲染规定,实现网站页面的绘制,并最终实现页面的展现。

第五步:敞开TCP连贯(四次挥手)

http和https的区别

Http:超文本传输协定(Http,HyperText Transfer Protocol)是互联网上利用最为宽泛的一种网络协议。设计Http最后的目标是为了提供一种公布和接管HTML页面的办法。它能够使浏览器更加高效。Http协定是以明文形式发送信息的,如果黑客截取了Web浏览器和服务器之间的传输报文,就能够间接取得其中的信息。

Https:是以平安为指标的Http通道,是Http的平安版。Https的平安根底是SSL。SSL协定位于TCP/IP协定与各种应用层协定之间,为数据通讯提供平安反对。SSL协定可分为两层:SSL记录协定(SSL Record Protocol),它建设在牢靠的传输协定(如TCP)之上,为高层协定提供数据封装、压缩、加密等基本功能的反对。SSL握手协定(SSL Handshake Protocol),它建设在SSL记录协定之上,用于在理论的数据传输开始前,通信单方进行身份认证、协商加密算法、替换加密密钥等。

HTTP与HTTPS的区别

1、HTTP是超文本传输协定,信息是明文传输,HTTPS是具备安全性的SSL加密传输协定。

2、HTTPS协定须要ca申请证书,个别收费证书少,因此须要肯定费用。

3、HTTP和HTTPS应用的是齐全不同的连贯形式,用的端口也不一样。前者是80,后者是443。

4、HTTP连贯是无状态的,HTTPS协定是由SSL+HTTP协定构建的可进行加密传输、身份认证的网络协议,安全性高于HTTP协定。

https的长处

只管HTTPS并非相对平安,把握根证书的机构、把握加密算法的组织同样能够进行中间人模式的攻打,但HTTPS仍是现行架构下最平安的解决方案,次要有以下几个益处:

1)应用HTTPS协定可认证用户和服务器,确保数据发送到正确的客户机和服务器;

2)HTTPS协定是由SSL+HTTP协定构建的可进行加密传输、身份认证的网络协议,要比http协定平安,可避免数据在传输过程中不被窃取、扭转,确保数据的完整性。

3)HTTPS是现行架构下最平安的解决方案,尽管不是相对平安,但它大幅减少了中间人攻打的老本。

4)谷歌曾在2014年8月份调整搜索引擎算法,并称“比起等同HTTP网站,采纳HTTPS加密的网站在搜寻后果中的排名将会更高”。

Https的毛病

1)Https协定握手阶段比拟费时,会使页面的加载工夫缩短近。

2)Https连贯缓存不如Http高效,会减少数据开销,甚至已有的安全措施也会因而而受到影响;

3)SSL证书通常须要绑定IP,不能在同一IP上绑定多个域名,IPv4资源不可能撑持这个耗费。

4)Https协定的加密范畴也比拟无限。最要害的,SSL证书的信用链体系并不平安,特地是在某些国家能够管制CA根证书的状况下,中间人攻打一样可行。

for 循环

for

在for循环中,循环获得数组或是数组相似对象的值,譬如arguments和HTMLCollection对象。

有余:

  • 在于每次循环的时候数组的长度都要去获取;
  • 终止条件要明确;
foreach() map()

两个办法都能够遍历到数组的每个元素,而且参数统一;

不同点:

forEach() : 对数组的每个元素执行一次提供的函数, 总是返回undefined;
map() : 创立一个新数组,其后果是该数组中的每个元素都调用一个提供的函数后返回的后果。 返回值是一个新的数组;

var array1 = [1,2,3,4,5]; var x = array1.forEach((value,index) => {    console.log(value);    return value + 10;});console.log(x);   // undefined var y = array1.map((value,index) => {    console.log(value);    return value + 10;});console.log(y);   // [11, 12, 13, 14, 15] 
for in

常常用来迭代对象的属性或数组的每个元素,它蕴含以后属性的名称或以后数组元素的索引。
当遍历一个对象的时候,变量 i 是循环计数器 为 对象的属性名, 以任意程序遍历一个对象的可枚举属性。对于每个不同的属性,语句都会被执行。
当遍历一个数组的时候,变量 i 是循环计数器 为 以后数组元素的索引

有余:

for..in循环会把某个类型的原型(prototype)中办法与属性给遍历进去.

const array = ["admin","manager","db"]; array.color = 'red';array.prototype.name= "zhangshan"; for(var i in array){    if(array.hasOwnProperty(i)){         console.log(array[i]);  // admin,manager,db,color    }}// hasOwnProperty(): 对象的属性或办法是非继承的,返回true
for … of

迭代循环可迭代对象(包含Array,Map,Set,String,TypedArray,arguments 对象)等等。不能遍历对象。只循环汇合自身的元素

var a = ['A', 'B', 'C'];var s = new Set(['A', 'B', 'C']);var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);a.name = 'array';for (var x of a) { console.log(x); //'A', 'B', 'C'}for (var x of s) { console.log(x);//'A', 'B', 'C'}for (var x of m) { console.log(x[0] + '=' + x[1]);//1='x',2='y',3='z'}

继承

// 定义一个动物类function Animal(name) {  // 属性  this.name = name || 'Animal';  // 实例办法  this.sleep = function(){    console.log(this.name + '正在睡觉!');  }}// 原型办法Animal.prototype.eat = function(food) {  console.log(this.name + '正在吃:' + food);};
原型链继承

外围: 将父类的实例作为子类的原型

function Dog(age) {  this.age = age;}Dog.protoType = New Animal();Dog.prototype.name = 'dog';const dog = new Dog(12);console.log(dog.name);console.log(dog.eat('age'));console.log(dog instanceof Animal); //true console.log(dog instanceof Dog); //true

new 创立新实例对象通过了以下几步:

  1. 创立一个新对象
  2. 将新对象的_proto_指向构造函数的prototype对象
  3. 将构造函数的作用域赋值给新对象 (也就是this指向新对象)
  4. 执行构造函数中的代码(为这个新对象增加属性)
  5. 返回新的对象
// 1. 创立一个新对象var Obj = {};// 2. 将新对象的_proto_指向构造函数的prototype对象Obj._proto_ =  Animal.prototype();// 3. 执行构造函数中的代码(为这个新对象增加属性) Animal.call(Obj);// 4. 返回新的对象return Obj;

重点:让新实例的原型等于父类的实例。

特点:

  1. 实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性
  2. 十分纯正的继承关系,实例是子类的实例,也是父类的实例
  3. 父类新增原型办法/原型属性,子类都能拜访到

    毛病:

  4. 新实例无奈向父类构造函数传参。
  5. 继承繁多。
  6. 所有新实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例批改了原型属性,另一个实例的原型属性也会被批改!)
  7. 要想为子类新增原型上的属性和办法,必须要在new Animal()这样的语句之后执行,不能放到结构器中
构造函数继承

外围:应用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

function Dog(name) {  Animal.apply(this, 'dog');  this.name = name;}const dog = new Dog();console.log(dog.name);console.log(dog.eat('age'));console.log(dog instanceof Animal); //false console.log(dog instanceof Dog); //true

重点:用.call()和.apply()将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复制))

特点:

1、只继承了父类构造函数的属性,没有继承父类原型的属性。
2、解决了原型链继承毛病1、2、3。
3、能够实现多继承,继承多个构造函数属性(call多个)。
4、在子实例中可向父实例传参。

毛病:

  1. 能继承父类构造函数的属性。
  2. 无奈实现构造函数的复用。(每次用每次都要从新调用)
  3. 每个新实例都有父类构造函数的正本,臃肿。
  4. 实例并不是父类的实例,只是子类的实例
组合继承(原型链继承和构造函数继承)(罕用)

外围:通过调用父类结构,继承父类的属性并保留传参的长处,而后通过将父类实例作为子类原型,实现函数复用

function Cat(name){  Animal.call(this, name);  this.name = name;}Cat.prototype = new Animal();Cat.prototype.constructor = Cat;var cat = new Cat();console.log(cat.name);console.log(cat.sleep());console.log(cat instanceof Animal); // trueconsole.log(cat instanceof Cat); // true

重点:联合了两种模式的长处,传参和复用

特点:

  1. 能够继承父类原型上的属性,能够传参,可复用。
  2. 每个新实例引入的构造函数属性是公有的。
  3. 既是子类的实例,也是父类的实例

毛病:

调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数。

原型式继承

    

重点:用一个函数包装一个对象,而后返回这个函数的调用,这个函数就变成了个能够随便削减属性的实例或对象。object.create()就是这个原理。

特点:相似于复制一个对象,用函数来包装。

毛病:
1、所有实例都会继承原型上的属性。
2、无奈实现复用。(新实例属性都是前面增加的)

寄生式继承

  
  
重点:就是给原型式继承里面套了个壳子。

长处:没有创立自定义类型,因为只是套了个壳子返回对象(这个),这个函数牵强附会就成了创立的新对象。

毛病:没用到原型,无奈复用。

寄生组合式继承(罕用)

寄生:在函数内返回对象而后调用
组合:
1、函数的原型等于另一个实例。
2、在函数中用apply或者call引入另一个构造函数,可传参 

function Cat(name){  Animal.call(this);  this.name = name || 'Tom';}(function(){  // 创立一个没有实例办法的类  var Super = function(){};  Super.prototype = Animal.prototype;  //将实例作为子类的原型  Cat.prototype = new Super();})();var cat = new Cat();Cat.prototype.constructor = Cat; // 须要修复下构造函数console.log(cat.name);console.log(cat.sleep());console.log(cat instanceof Animal); // trueconsole.log(cat instanceof Cat); //true

重点:修复了组合继承的问题