HTML

块级元素与行内元素

下表列出了行内元素和块级元素的主要区别
html中行内元素和块级元素的区别

块级元素 行内元素
独占一行,每一个块级元素都会从新的一行重新开始,从上到下排布 和其他内联元素从左到右在一行显示
可以直接控制宽度、高度以及盒子模型的相关css属性 不能直接控制宽度、高度以及盒子模型的相关css属性,但是直接设置内外边距的左右值是可以的
在不设置宽度的情况下,块级元素的宽度是它父级元素内容的宽度 内联元素的宽高是由本身内容的大小决定(文字、图片等)
在不设置高度的情况下,块级元素的高度是它本身内容的高度 内联元素只能容纳文本或者其他内联元素(此处请注意,不要在内联元素中嵌套块级元素)
display:block display:inline

常见的块级元素:


常见的行内元素
前端知识点整合 - 图1

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>块级元素与行内元素</title>
    <style type="text/css">
        div {
            width: 120px;
            height: 120px;
            margin: 50px 50px;
            padding: 50px 40px;
            background:cadetblue;
        }

        span {
            width: 120px;
            height: 120px;
            margin: 1000px 20px;
            padding: 50px 40px;
            background: lightblue;
            padding-top: 100px;
        }

        i {
            width: 120px;
            height: 120px;
            margin: 1000px 20px;
            padding: 50px 40px;
            background:darkgoldenrod;
        }

        label {
            word-break: break-all;
            border: black 1px solid;
            /* padding: 5px; */
        }
    </style>
</head>

<body>
    <i>自动换行</i>
    <div>块状元素</div>
    <div>块状元素</div>
    <i>不会自动换行</i>
    <span>行内元素</span>
    <div></div>
    <label>jdijfiado.djiafjdos.afjdsiafjosdfs.adzfzx.fsaodfjkpjdijfiado.djiafjdos.afjdsiafjosdfs.adzfzx.fsaodfjkpjdijfiado.djiafjdos.afjdsiafjosdfs.adzfzx.fsaodfjkpjdijfiado.djiafjdos.afjdsiafjosdfs.adzfzx.fsaodfjkpjdijfiado.djiafjdos.afjdsiafjosdfs.adzfzx.fsaodfjkpjdijfiado.djiafjdos.afjdsiafjosdfs.adzfzx.fsaodfjkpjdijfiado.djiafjdos.afjdsiafjosdfs.adzfzx.fsaodfjkpjdijfiado.djiafjdos.afjdsiafjosdfs.adzfzx.fsaodfjkpjdijfiado.djiafjdos.afjdsiafjosdfs.adzfzx.fsaodfjkpjdijfiado.djiafjdos.afjdsiafjosdfs.adzfzx.fsaodfjkpjdijfiado.djiafjdos.afjdsiafjosdfs.adzfzx.fsaodfjkpjdijfiado.djiafjdos.afjdsiafjosdfs.adzfzx.fsaodfjkpjdijfiado.djiafjdos.afjdsiafjosdfs.adzfzx.fsaodfjkpjdijfiado.djiafjdos.afjdsiafjosdfs.adzfzx.fsaodfjkpjdijfiado.djiafjdos.afjdsiafjosdfs.adzfzx.fsaodfjkpjdijfiado.djiafjdos.afjdsiafjosdfs.adzfzx.fsaodfjkpjdijfiado.djiafjdos.afjdsiafjosdfs.adzfzx.fsaodfjkpjdijfiado.djiafjdos.afjdsiafjosdfs.adzfzx.fsaodfjkpjdijfiado.djiafjdos.afjdsiafjosdfs.adzfzx.fsaodfjkpjdijfiado.djiafjdos.afjdsiafjosdfs.adzfzx.fsaodfjkp</label>
</body>

</html>

CSS

盒模型

前端知识点整合 - 图2
所有HTML元素可以看作盒子,在CSS中,”box model”这一术语是用来设计和布局时使用。
CSS盒模型本质上是一个盒子,封装周围的HTML元素,它包括:边距,边框,填充,和实际内容。
盒模型允许我们在其它元素和周围元素边框之间的空间放置元素。

一般有两种盒模型:content-box 标准盒模型、border-box 怪异盒模型(实际上padding-box,margin-box的概念提出过,但未被实现)
盒模型主要影响宽高(width、height)属性的表现,content-box下,宽高等于内容的宽高,border-box下,宽高等于border+padding+content的宽高。
前端知识点整合 - 图3前端知识点整合 - 图4

定位position

static
absolute
relative
fixed
sticky

布局display

none
block
inline
inline-block
flex
grid

浮动

float

7阶层叠水平

前端知识点整合 - 图5
前端知识点整合 - 图6

css样式优先级(权重)

样式优先级规则:

  1. 根据权重值排序,应用权重值第一的样式
  2. 如果权重值相同,则应用最后定义的样式(应避免这种情况,若出现问题,溯源的时候比较麻烦)

    权重规则:

    权重分为四个等级(有一个重要级角色不在此列)
  • 第一等:内联样式,权重1,0,0,0,即标签内的style属性设置的样式
  • 第二等:ID选择器,权重0,1,0,0,例如#id{…}
  • 第三等:类选择器,伪类选择器,属性选择器,权重0,0,1,0,例如.class{…}、:hover{…}、[arrtibute=value]
  • 第四等:标签选择器,伪元素选择器,权重0,0,0,1,例如div{…}、::after{…}
  • 超然地位:!important(只要我出现,不好意思,权重就是无限,优先考虑,别的靠边站)
  • 继承的样式没有权值。

    PS:还有几个权重为0,不计入排名,他们就是通配选择器(*),子选择器(>),相邻同胞选择器(+),即使是0,也比继承的样式高。
    根据样式根据以上规则,按照选择器累加计算权重,例如
    #my-id .my-class div p{…}
    对于p标签的来说,这个样式的权重就是100+10+1+1=112,如果p还有别的样式,只要小于(严格小于)112,就使用这个样式,别的样式无效。
      具体可见http://www.w3.org/TR/CSS2/cascade.html#specificity
    https://developer.mozilla.org/zh-CN/docs/Web/CSS/Specificity

    JS

    原型、原型链

    当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象( object )都有一个私有属性(称之为 proto )指向它的构造函数的原型对象(prototype )。该原型对象也有一个自己的原型对象( proto ) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。
    几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。

    基于原型链的继承

    继承属性

    JavaScript 对象是动态的属性“包”(指其自己的属性)。JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。

这里演示当尝试访问属性时会发生什么:

// 让我们从一个自身拥有属性a和b的函数里创建一个对象o:
let f = function () {
   this.a = 1;
   this.b = 2;
}
/* 这么写也一样
function f() {
  this.a = 1;
  this.b = 2;
}
*/
let o = new f(); // {a: 1, b: 2}
// 在f函数的原型上定义属性
f.prototype.b = 3;
f.prototype.c = 4;
// 不要在 f 函数的原型上直接定义 f.prototype = {b:3,c:4};这样会直接打破原型链
// o.[[Prototype]] 有属性 b 和 c
//  (其实就是 o.__proto__ 或者 o.constructor.prototype)
// o.[[Prototype]].[[Prototype]] 是 Object.prototype.
// 最后o.[[Prototype]].[[Prototype]].[[Prototype]]是null
// 这就是原型链的末尾,即 null,
// 根据定义,null 就是没有 [[Prototype]]。
// 综上,整个原型链如下: 
// {a:1, b:2} ---> {b:3, c:4} ---> Object.prototype---> null
console.log(o.a); // 1
// a是o的自身属性吗?是的,该属性的值为 1
console.log(o.b); // 2
// b是o的自身属性吗?是的,该属性的值为 2
// 原型上也有一个'b'属性,但是它不会被访问到。
// 这种情况被称为"属性遮蔽 (property shadowing)"
console.log(o.c); // 4
// c是o的自身属性吗?不是,那看看它的原型上有没有
// c是o.[[Prototype]]的属性吗?是的,该属性的值为 4
console.log(o.d); // undefined
// d 是 o 的自身属性吗?不是,那看看它的原型上有没有
// d 是 o.[[Prototype]] 的属性吗?不是,那看看它的原型上有没有
// o.[[Prototype]].[[Prototype]] 为 null,停止搜索
// 找不到 d 属性,返回 undefined

继承方法

JavaScript 并没有其他基于类的语言所定义的“方法”。在 JavaScript 里,任何函数都可以添加到对象上作为对象的属性。函数的继承与其他的属性继承没有差别,包括上面的“属性遮蔽”(这种情况相当于其他语言的方法重写)。
当继承的函数被调用时,this 指向的是当前继承的对象,而不是继承的函数所在的原型对象。

var o = {
  a: 2,
  m: function(){
    return this.a + 1;
  }
};
console.log(o.m()); // 3
// 当调用 o.m 时,'this' 指向了 o.
var p = Object.create(o);
// p是一个继承自 o 的对象
p.a = 4; // 创建 p 的自身属性 'a'
console.log(p.m()); // 5
// 调用 p.m 时,'this' 指向了 p
// 又因为 p 继承了 o 的 m 函数
// 所以,此时的 'this.a' 即 p.a,就是 p 的自身属性 'a'

new运算符

new 运算符主要做了下面几件事

  • 创建一个空对象{}
  • 将这个空对象的proto成员指向了调用函数对象prototype成员对象
  • 将函数对象的this指针替换成obj,然后再调用函数

类似下面的代码

var clazz = new Class();
=========================
var clazz = {};
clazz.__proto__ = Class.prototype;
Class.call(obj);

this

https://blog.csdn.net/cjgeng88/article/details/79846670

  1. 默认绑定
  2. 隐式绑定
  3. 显式绑定
  4. new绑定 ```javascript function foo() { console.log( this.a ); }

var a = 2; // 1.默认绑定 // 这种直接使用而不带任何修饰的函数调用 ,就 默认且只能 应用 默认绑定 foo();

var obj = { a: 3, foo: foo }; // 隐性绑定,函数foo执行的时候有了上下文对象,即 obj。 // 这种情况下,函数里的this默认绑定为上下文对象,即obj.a obj.foo();

var obj2 = { a: 4, obj: obj }; // 链式隐性绑定 obj2.obj.foo();

// 隐式丢失 var bar = obj.foo; bar(); // 隐式丢失 setTimeout( obj.foo, 100 );

// 显示绑定 foo.call( obj ); foo.call( obj2 );

var bar = function(){ foo.call( obj ); } setTimeout( bar, 100 ); // 3 bar.call( obj2 );

function foo(a) { this.a = a; }

var a = 2; // new 绑定 var bar1 = new foo(3); console.log(bar1.a); // ?

var bar2 = new foo(4); console.log(bar2.a);

绑定规则优先级<br />优先级是这样的,按照下面的顺序来进行判断:<br />变量是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。<br />变量是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是 指定的对象。<br />变量是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。<br />如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到 全局对象。  

```javascript
// 1. 正常调用
var foo = () => {   
    console.log( this.a );
}

var a = 2;

var obj = { 
    a: 3,
    foo: foo 
};

obj.foo(); //2
foo.call(obj); //2 ,箭头函数中显示绑定不会生效

// 2. 函数回调
function foo(){ 
    return () => {
        console.log( this.a );
    }   
}

var a = 2;

var obj = { 
    a: 3,
    foo: foo 
};

var bar = obj.foo();
bar(); //3

call,apply,bind

  • 在JavaScript中,callapplybindFunction对象自带的三个方法,都是为了改变函数体内部 this 的指向。
  • apply 、 call 、bind 三者第一个参数都是 this 要指向的对象,也就是想指定的上下文;
  • apply 、 call 、bind 三者都可以利用后续参数传参;
  • bind 是返回对应 函数,便于稍后调用;apply 、call 则是立即调用 。
function foo(a,b){
    console.log(a+b);
}
foo.call(null,'海洋','饼干');
foo.apply(null, ['海洋','饼干'] );

function foo(){
    console.log(this.a);
}
var obj = { a : 10 };

foo = foo.bind(obj);
foo();

call语法

  • fun.call(thisArg, arg1, arg2, ...)
  • thisArg: 在fun函数运行时指定的this值。需要注意的是,指定的this值并不一定是该函数执行时真正的this值,如果这个函数处于非严格模式下,则指定为null和undefined的this值会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。
  • arg1, arg2, ... 指定的参数列表

    apply语法

  • fun.apply(thisArg, [argsArray])

  • thisArg 在 fun 函数运行时指定的 this 值。需要注意的是,指定的 this 值并不一定是该函数执行时真正的 this 值,如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。
  • argsArray 一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 fun 函数。如果该参数的值为null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。

    bind语法

  • fun.bind(thisArg[, arg1[, arg2[, ...]]])

  • thisArg 当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用new 操作符调用绑定函数时,该参数无效。
  • arg1, arg2, ... 当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。

typeof,instanceof,Object.prototype.toString.call

typeof

https://tc39.github.io/ecma262/#sec-typeof-operator
typeof操作符返回一个字符串,表示未经计算的操作数的类型。

下表总结了typeof可能的返回值。有关类型和原始值的更多信息,可查看 JavaScript数据结构 页面。

类型 结果
Undefined "undefined"
Null "object"(见下文)
Boolean "boolean"
Number "number"
String "string"
Symbol (ECMAScript 6 新增) "symbol"
宿主对象(由JS环境提供) Implementation-dependent
函数对象([[Call]] 在ECMA-262条款中实现了) "function"
任何其他对象 "object"

JavaScript的值是由一个类型标签和实际值表示的,类型标签占3位,比如: 000就表示object。JavaScript中的null表示机器码中的NULL(空指针),而在大多数平台下NULL是0x00(十六进制),所以null是00000000,标签位也是000,故 typeof null === “object”。

// Numbers
typeof 37 === 'number';
typeof 3.14 === 'number';
typeof Math.LN2 === 'number';
typeof Infinity === 'number';
typeof NaN === 'number'; // 尽管NaN是"Not-A-Number"的缩写,意思是"不是一个数字"
typeof Number(1) === 'number'; // 不要这样使用!

// Strings
typeof "" === 'string';
typeof "bla" === 'string';
typeof (typeof 1) === 'string'; // typeof返回的肯定是一个字符串
typeof String("abc") === 'string'; // 不要这样使用!

// Booleans
typeof true === 'boolean';
typeof false === 'boolean';
typeof Boolean(true) === 'boolean'; // 不要这样使用!

// Symbols
typeof Symbol() === 'symbol';
typeof Symbol('foo') === 'symbol';
typeof Symbol.iterator === 'symbol';

// Undefined
typeof undefined === 'undefined';
typeof blabla === 'undefined'; // 一个未定义的变量,或者一个定义了却未赋初值的变量

// Objects
typeof {a:1} === 'object';
// 使用Array.isArray或者Object.prototype.toString.call方法可以从基本的对象中区分出数组类型
typeof [1, 2, 4] === 'object';
typeof new Date() === 'object';
// 下面的容易令人迷惑,不要这样使用!
typeof new Boolean(true) === 'object';
typeof new Number(1) === 'object';
typeof new String("abc") === 'object';
// 从JavaScript一开始出现就是这样的 
typeof null === 'object';
// 正则表达式
typeof /s/ === 'object'; // Chrome 12+ , 符合 ECMAScript 5.1
typeof /s/ === 'object'; // Firefox 5+ , 符合 ECMAScript 5.1

// 函数
typeof function(){} === 'function';
typeof Math.sin === 'function';
typeof /s/ === 'function'; // Chrome 1-12 , 不符合 ECMAScript 5.1

instanceof

instanceof运算符用于测试构造函数的prototype属性是否出现在对象的原型链中的任何位置。

// 定义构造函数
function C(){} 
function D(){} 

var o = new C();


o instanceof C; // true,因为 Object.getPrototypeOf(o) === C.prototype


o instanceof D; // false,因为 D.prototype不在o的原型链上

o instanceof Object; // true,因为Object.prototype.isPrototypeOf(o)返回true
C.prototype instanceof Object // true,同上

C.prototype = {};
var o2 = new C();

o2 instanceof C; // true

o instanceof C; // false,C.prototype指向了一个空对象,这个空对象不在o的原型链上.

D.prototype = new C(); // 继承
var o3 = new D();
o3 instanceof D; // true
o3 instanceof C; // true 因为C.prototype现在在o3的原型链上

需要注意的是,如果表达式 obj instanceof Foo 返回true,则并不意味着该表达式会永远返回true,因为Foo.prototype属性的值有可能会改变,改变之后的值很有可能不存在于obj的原型链上,这时原表达式的值就会成为false。另外一种情况下,原表达式的值也会改变,就是改变对象obj的原型链的情况,虽然在目前的ES规范中,我们只能读取对象的原型而不能改变它,但借助于非标准的__proto__伪属性,是可以实现的。比如执行obj.__proto__ = {}之后,obj instanceof Foo就会返回false了。
在浏览器中,我们的脚本可能需要在多个窗口之间进行交互。多个窗口意味着多个全局环境,不同的全局环境拥有不同的全局对象,从而拥有不同的内置类型构造函数。这可能会引发一些问题。比如,表达式 [] instanceof window.frames[0].Array 会返回false,因为 Array.prototype !== window.frames[0].Array.prototype,并且数组从前者继承。
起初,你会认为这样并没有意义,但是当你在你的脚本中开始处理多个frame或多个window以及通过函数将对象从一个窗口传到另一个窗口时,这就是一个有效而强大的话题。比如,实际上你可以通过使用 Array.isArray(myObj) 或者Object.prototype.toString.call(myObj) === "[object Array]"来安全的检测传过来的对象是否是一个数组。
比如检测一个Nodes在另一个窗口中是不是SVGElement,你可以使用myNode instanceof myNode.ownerDocument.defaultView.SVGElement

下面的代码使用了instanceof来证明:String和``Date对象同时也属于Object类型(他们是由Object类派生出来的)。
但是,使用对象文字符号创建的对象在这里是一个例外:虽然原型未定义,但instanceof Object返回true。

var simpleStr = "This is a simple string"; 
var myString  = new String();
var newStr    = new String("String created with constructor");
var myDate    = new Date();
var myObj     = {};
var myNonObj  = Object.create(null);
simpleStr instanceof String; // 返回 false, 检查原型链会找到 undefined
myString  instanceof String; // 返回 true
newStr    instanceof String; // 返回 true
myString  instanceof Object; // 返回 true
myObj instanceof Object;    // 返回 true, 尽管原型没有定义
({})  instanceof Object;    // 返回 true, 同上
myNonObj instanceof Object; // 返回 false, 一种创建对象的方法,这种方法创建的对象不是Object的一个实例
myString instanceof Date; //返回 false
myDate instanceof Date;     // 返回 true
myDate instanceof Object;   // 返回 true
myDate instanceof String;   // 返回 false

演示mycar属于Car类型的同时又属于Object类型
下面的代码创建了一个类型Car,以及该类型的对象实例mycar. instanceof运算符表明了这个mycar对象既属于Car类型,又属于Object类型。

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}
var mycar = new Car("Honda", "Accord", 1998);
var a = mycar instanceof Car;    // 返回 true
var b = mycar instanceof Object; // 返回 true

Object.prototype.toString.call

使用typeof无法区分null、array、date
instanceof无法运用在值类型区分
没事,我们还有一种方法,Object.prototype.toString.call()
Object.prototype.toString.call()是一种比较常用,也是比较有效的方法

var toString = Object.prototype.toString;
// Numbers 都返回[object Number]
toString.call(37);
toString.call(3.14);
toString.call(Math.LN2);
toString.call(Infinity);
toString.call(Number(1));

// Strings 都返回[object String]
toString.call("");
toString.call("bla");
toString.call(String("abc"));

// Booleans 都返回[object Boolean]
toString.call(true);
toString.call(false);
toString.call(Boolean(true));

// Symbols 都返回[object Symbol]
toString.call(Symbol());
toString.call(Symbol('foo'));
toString.call(Symbol.iterator);

// Undefined 都返回[object Undefined]
toString.call(undefined);
//toString.call(blabla);//不同 一个未定义的变量会报错

// Objects
toString.call({a:1});//[object Object]
toString.call([1, 2, 4]);//[object Array] 不同
toString.call(new Date());//[object Date] 不同
toString.call(new Boolean(true));//[object Boolean] 不同
toString.call(new Number(1));//[object Number] 不同
toString.call(new String("abc"));//[object String] 不同
toString.call(null);//[object Null] 不同
toString.call(/s/);//[object RegExp] 不同
toString.call(new TypeError());//[object Error] 不同

// 函数 都返回[object Function]
toString.call(function(){});
toString.call(Math.sin);

为什么Object.prototype.toString.call()可以区分数据类型
**我们先来了解一下在调用Object.prototype.toString.call()的时候发生了什么

  1. 在ECMA5中,调用Object.prototype.toString会执行以下步骤:

前端知识点整合 - 图7
(1).如果值为undefined,则返回”[object Undefiend]”
(2).如果值为null,则返回”[object Null]”
(3).将传递的值调用ToObject方法,并赋给O作为调用后的结果
(4).将O的内部属性[[Class]]的值赋给class
(5).结果返回字符串,该字符串由三个字符串”[object “, class, and “]”拼接而成
根据上面的描述我们可以看出来,主要通过对象的内置属性[[Class]]来判断
**

  1. 内置属性[[Class]]

ECMA规范中这么介绍[[Class]]
前端知识点整合 - 图8
前端知识点整合 - 图9
翻译:本规范针对每种内置对象定义了[[Class]]内部属性的值。对象的[[Class]]内部属性的值可以是除了。。。之外的任何字符串值。[[Class]]内部属性的值用于内部区分不同类型的对象。 请注意,除了通过Object.prototype.toString外,本规范没有提供任何方法让程序访问该值。
我们再来看规范中,在创建某种类型变量的时候,会对对象内置属性[[Class]]进行赋值

  • 创建数组时:

前端知识点整合 - 图10

  • 创建函数时:

前端知识点整合 - 图11

  • 创建字符串时:

前端知识点整合 - 图12

  1. ES6中不再使用[[Class]]内置属性进行判断

最新规范中不再使用[[Class]]内置属性进行判断,在调用Object.prototype.toString时执行了以下步骤:
前端知识点整合 - 图13

typeof ? instanceof ? Object.prototype.toString.call(?)
var test = ‘Chris’; string test instanceof String//false [object String]
var test = 27; number test instanceof Number//false [object Number]
var test = true; boolean test instanceof Boolean//false [object Boolean]
var test = [1,2,3]; object test instanceof Array//true [object Array]
test instanceof Object//true
var test = null; object test instanceof Object//false [object Null]
var test = undefined; undefined test instanceof Object//false [object Undefined]
var test = new String(‘xuriliang’) object test instanceof String//true [object String]
test instanceof Object//true
var test = new Number(27) object test instanceof Number//true [object Number]
test instanceof Object//true
var test = new Boolean(true) object test instanceof Boolean//true [object Boolean]
test instanceof Object//true
var test = new Array(1,2,3) object test instanceof Array//true [object Array]
test instanceof Object//true
var test = function(){} function test instanceof Function//true [object Function]
test instanceof Object//true
var test = /d/ object test instanceof RegExp//true [object RegExp]
test instanceof Object//true

IIFE

定义

IIFE(Immediately Invoked Function Expression)是一种高级用法,考虑这样一种场景,你和几十个小伙伴开发一个大型软件,而你们在使用变量名时往往会用到重复的变量名,而js是可以重复声明变量的,噩梦来了,你的代码可能会在各种场合发生莫名其妙的错误,为了解决这一点,我们可以使用IIFE。
IIFE的出现是为了弥补JS在scope方面的缺陷:JS只有全局作用域(global scope)、函数作用域(function scope),从ES6开始才有块级作用域(block scope)。对比现在流行的其他面向对象的语言可以看出,JS在访问控制这方面是多么的脆弱!那么如何实现作用域的隔离呢?在JS中,只有function,只有function,只有function才能实现作用域隔离,因此如果要将一段代码中的变量、函数等的定义隔离出来,只能将这段代码封装到一个函数中。
在我们通常的理解中,将代码封装到函数中的目的是为了复用。在JS中,当然声明函数的目的在大多数情况下也是为了复用,但是JS迫于作用域控制手段的贫乏,我们也经常看到只使用一次的函数:这通常的目的是为了隔离作用域了!既然只使用一次,那么立即执行好了!既然只使用一次,函数的名字也省掉了!这就是IIFE的由来。

常见形式

根据最后表示函数执行的一对()位置的不同,常见的IIFE写法有两种,示例如下:
列表1:IIFE写法一

(function foo(){
  var a = 10;
  console.log(a);
})();

列表2:IIFE写法二

(function foo(){
  var a = 10;
  console.log(a);
}());

这两种写法效果完全一样,使用哪种写法取决于你的风格,貌似第一种写法比较常见。
其实,IIFE不限于()的表现形式[1],但是还是遵守约定俗成的习惯比较好。

实战

如果看过JQuery源码的话,就可以看到JQuery也是使用IIFE来实现变量隔离的。

// 非IIFE
for(var i=0; i<10; i++) {
    setTimeout(()=>{console.log(i);});
}

// IIFE
for(var i=0; i<10; i++) {
    (function(i){
    setTimeout(()=>{console.log(i);})
  })(i);
}

闭包

简单地理解,闭包就是函数内部的函数,他能访问到所有外部函数里的所有变量。
你可以在一个函数里面嵌套另外一个函数。嵌套(内部)函数对其容器(外部)函数是私有的。它自身也形成了一个闭包。一个闭包是一个可以自己拥有独立的环境与变量的的表达式(通常是函数)。
既然嵌套函数是一个闭包,就意味着一个嵌套函数可以”继承”容器函数的参数和变量。换句话说,内部函数包含外部函数的作用域。
可以总结如下:

  • 内部函数只可以在外部函数中访问。
  • 内部函数形成了一个闭包:它可以访问外部函数的参数和变量,但是外部函数却不能使用它的参数和变量。
function outer() {
     var  a = '变量1';
     var  inner = function () {
       console.info(a);
     }
    return inner;    // inner 就是一个闭包函数,因为他能够访问到outer函数的作用域
}

由于内部函数形成了闭包,因此你可以调用外部函数并为外部函数和内部函数指定参数:

function outside(x) {
  function inside(y) {
    return x + y;
  }
  return inside;
}
fn_inside = outside(3); // Think of it like: give me a function that adds 3 to whatever you give it
result = fn_inside(5); // returns 8
result1 = outside(3)(5); // returns 8

使用闭包,你甚至能模仿Java中的类:

var createPet = function(name) {
  var sex;

  return {
    setName: function(newName) {
      name = newName;
    },

    getName: function() {
      return name;
    },

    getSex: function() {
      return sex;
    },

    setSex: function(newSex) {
      if(typeof newSex == "string" 
        && (newSex.toLowerCase() == "male" || newSex.toLowerCase() == "female")) {
        sex = newSex;
      }
    }
  }
}

var pet = createPet("Vivie");
pet.getName();                  // Vivie

pet.setName("Oliver");
pet.setSex("male");
pet.getSex();                   // male
pet.getName();                  // Oliver

函数、变量提升

事件捕获与事件冒泡

事件冒泡
事件冒泡是由IE开发团队提出来的,即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播。
事件捕获
事件捕获是由Netscape Communicator团队提出来的,是先由最上一级的节点先接收事件,然后向下传播到具体的节点。

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>Event Bubbling Example</title>
    </head>
    <style type="text/css">
        #child {
            width: 100px;
            height: 100px;
            background-color: #FF0000;
        }
    </style>
    <body>
        <div id="parent">
            <div id="child"></div>
        </div>
    </body>
    <script type="text/javascript">
        var parent = document.getElementById("parent");
        var child = document.getElementById("child");
        child.onclick = function(event) {
            alert("child");
        };
        document.body.addEventListener("click", function(event) {
            alert("body:event bubble");
        }, false);
        parent.addEventListener("click", function(event) {
            alert("parent:event bubble");
        }, false);
        document.body.addEventListener("click", function(event) {
            alert("body:event catch");
        }, true);
        parent.addEventListener("click", function(event) {
            alert("parent:event catch");
        }, true);
    </script>
</html>

输出顺序:body:event catch—>parent:event catch—>child—>parent:event bubble—>body:event bubble
实际发生的情况如下图,DOM事件流一般分成三个阶段,捕获阶段—>目标阶段—>冒泡阶段。
前端知识点整合 - 图14

Promise

事件循环

JS从诞生之日起,就是一门单线程的非阻塞的脚本语言。这是由其最初的用途所决定的:与浏览器交互。基本上所有语言和UI相关的部分都是单线程的,比如安卓的UI线程绘制。如果是多线程的话,两个线程同时操作一个元素的属性,就会造成后续一系列问题。

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。
js引擎执行异步代码而不用等待,是因有为有 消息队列和事件循环。

消息队列:消息队列是一个先进先出的队列,它里面存放着各种消息。
事件循环:事件循环是指主线程重复从消息队列中取消息、执行的过程。

实际上,主线程只会做一件事情,就是从消息队列里面取消息、执行消息,再取消息、再执行。当消息队列为空时,就会等待直到消息队列变成非空。而且主线程只有在将当前的消息执行完成后,才会去取下一个消息。这种机制就叫做事件循环机制,取一个消息并执行的过程叫做一次循环。
事件循环用代码表示大概是这样的:

while(true) {
    var message = queue.get();
    execute(message);
}

那么,消息队列中放的消息具体是什么东西?消息的具体结构当然跟具体的实现有关,但是为了简单起见,我们可以认为:

消息就是注册异步任务时添加的回调函数。

再次以异步AJAX为例,假设存在如下的代码:

$.ajax('http://segmentfault.com', function(resp) {
    console.log('我是响应:', resp);
});
// 其他代码
...
...
...

主线程在发起AJAX请求后,会继续执行其他代码。AJAX线程负责请求segmentfault.com,拿到响应后,它会把响应封装成一个JavaScript对象,然后构造一条消息:

// 消息队列中的消息就长这个样子
var message = function () {
    callbackFn(response);
}

其中的callbackFn就是前面代码中得到成功响应时的回调函数。
主线程在执行完当前循环中的所有代码后,就会到消息队列取出这条消息(也就是message函数),并执行它。到此为止,就完成了工作线程对主线程的通知,回调函数也就得到了执行。如果一开始主线程就没有提供回调函数,AJAX线程在收到HTTP响应后,也就没必要通知主线程,从而也没必要往消息队列放消息。
用图表示这个过程就是:
image.png
从上文中我们也可以得到这样一个明显的结论,就是:

异步过程的回调函数,一定不在当前这一轮事件循环中执行。

事件循环进阶:macrotask与microtask(宏观任务与微观任务)

一张图展示JavaScript中的事件循环:
image.png

一次事件循环:先运行macroTask队列中的一个,然后运行microTask队列中的所有任务。接着开始下一次循环(只是针对macroTask和microTask,一次完整的事件循环会比这个复杂的多)。
JS中分为两种任务类型:macrotask和microtask,在ECMAScript中,microtask称为jobs,macrotask可称为task
它们的定义?区别?简单点可以按如下理解:
macrotask(又称之为宏任务),可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)
每一个task会从头到尾将这个任务执行完毕,不会执行其它
浏览器为了能够使得JS内部task与DOM任务能够有序的执行,会在一个task执行结束后,在下一个 task 执行开始前,对页面进行重新渲染
(task->渲染->task->…)
microtask(又称为微任务),可以理解是在当前 task 执行结束后立即执行的任务
也就是说,在当前task任务后,下一个task之前,在渲染之前
所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染
也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)
那么什么样的场景会形成macrotask和microtask呢?

macroTask: 主代码块, setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering(可以看到,事件队列中的每一个事件都是一个macrotask)

microTask: process.nextTick, Promise, Object.observe, MutationObserver (其实都是ES6内容)
补充:在node环境下,process.nextTick的优先级高于Promise,也就是可以简单理解为:在宏任务结束后会先执行微任务队列中的nextTickQueue部分,然后才会执行微任务中的Promise部分。
另外,setImmediate则是规定:在下一次Event Loop(宏任务)时触发(所以它是属于优先级较高的宏任务),(Node.js文档中称,setImmediate指定的回调函数,总是排在setTimeout前面),所以setImmediate如果嵌套的话,是需要经过多个Loop才能完成的,而不会像process.nextTick一样没完没了。

实践:上代码
我们以setTimeout、process.nextTick、promise为例直观感受下两种任务队列的运行方式。

console.log('main1');
//process.nextTick(function() {
//    console.log('process.nextTick1');
//});
setTimeout(function() {
    console.log('setTimeout');
    //process.nextTick(function() {
    //    console.log('process.nextTick2');
    //});
}, 0);
new Promise(function(resolve, reject) {
    console.log('promise');
    resolve();
}).then(function() {
    console.log('promise then');
});
console.log('main2');

别着急看答案,先以上面的理论自己想想,运行结果会是啥?
最终结果是这样的:

main1
promise
main2
process.nextTick1
promise then
setTimeout
process.nextTick2

process.nextTick 和 promise then在 setTimeout 前面输出,已经证明了macroTask和microTask的执行顺序。但是有一点必须要指出的是。上面的图容易给人一个错觉,就是主进程的代码执行之后,会先调用macroTask,再调用microTask,这样在第一个循环里一定是macroTask在前,microTask在后。
但是最终的实践证明:在第一个循环里,process.nextTick1和promise then这两个microTask是在setTimeout这个macroTask里之前输出的,这是为什么呢?
因为主进程的代码也属于macroTask(这一点我比较疑惑的是主进程都是一些同步代码,而macroTask和microTask包含的都是一些异步任务,为啥主进程的代码会被划分为macroTask,不过从实践来看确实是这样,而且也有理论支撑:【翻译】Promises/A+规范)。
主进程这个macroTask(也就是main1、promise和main2)执行完了,自然会去执行process.nextTick1和promise then这两个microTask。这是第一个循环。之后的setTimeout和process.nextTick2属于第二个循环
别看上面那段代码好像特别绕,把原理弄清楚了,都一样 ~
requestAnimationFrame、Object.observe(已废弃) 和 MutationObserver这三个任务的运行机制大家可以从上面看到,不同的只是具体用法不同。重点说下UI rendering。在HTML规范:event-loop-processing-model里叙述了一次事件循环的处理过程,在处理了macroTask和microTask之后,会进行一次Update the rendering,其中细节比较多,总的来说会进行一次UI的重新渲染。

事件循环机制进一步补充

这里就直接引用一张图片来协助理解:(参考自Philip Roberts的演讲《Help, I’m stuck in an event-loop》)
image.png

上图大致描述就是:

  • 主线程运行时会产生执行栈,栈中的代码调用某些api时,它们会在事件队列中添加各种事件(当满足触发条件后,如ajax请求完毕)
  • 而栈中的代码执行完毕,就会读取事件队列中的事件,去执行那些回调
  • 如此循环
  • 注意,总是要等待栈中的代码执行完毕后才会去读取事件队列中的事件