前端知识总结--2 js部分

1。 JavaScript的『预解释』与『变量提高』javascript

先看如下代码输出啥?css

var a= 1;
function f() {
  console.log(a);
  var a = 2;
}
f();

首先答案是:undefined;html

JavaScript在浏览器中运行的过程分为两个阶段预解释阶段、执行阶段;

  1. 读取var a后,在当前做用域中查找是否有相同声明,若是没有就在当前做用域集合中建立一个名为a的变量,不然忽略此声明继续进行解析;
  2. 接下来,V8引擎会处理a = 2的赋值操做,首先会询问当前做用域中是否有名为a的变量,若是有进行赋值,不然继续向上级做用域询问

因此该题目中:在函数fn的做用域中,首先提取变量声明:var a;由于该做用域中有对a的赋值,因此不会继续查找上级做用域。且在赋值前打印,因此是undefined;vue

相似的看下题:java

var a= 1;
function f() {
  console.log(a);
}
f(); // 1
var a= 1;
function f() {
  console.log(a);
  var a;
}
f(); //undefined

函数声明与函数表达式

咱们看到,在编译器处理阶段,除了被var声明的变量会有变量提高这一特性以外,函数也会产生这一特性,可是函数声明与函数表达式两种范式建立的函数却表现出不一样的结果.node

f();
g();
//函数声明
function f() {
    console.log('f');
}
//函数表达式
var g = function() {
    console.log('g');
};

//fes6

//报错:VM693:2 Uncaught TypeError: g is not a functionsegmentfault

f() 好理解属于函数声明提高;可是对于函数表达式g,被赋予undefined,undefeated没法被执行而报错。浏览器

冲突处理

变量之间冲突app

var a = 3;
var a = 4;
console.log(a); //4

函数冲突

f();
function f() {
    console.log('f');
}

function f () {
    console.log('g');
};
// g

3.函数与变量之间冲突

console.log(f);

function f() {
    console.log('f');
}
var f ='g';

ƒ f() {
console.log('f');
}

说明函数覆盖了变量;

相似的let,存在暂时性死区:

 

function f() {
  console.log(a);
  let a = 2;
}
f(); 

 

报错: //ReferenceError: a is not defined

这段代码直接报错显示未定义,letconst拥有相似的特性,阻止了变量提高,当代码执行到console.log(a)时,执行换将中a还从未被定义,所以产生了错误

======================

JS的执行机制:

浅析JS中的堆内存与栈内存 

浅析javascript调用栈

https://www.cxymsg.com/guide/mechanism.html#javascript%E7%9A%84%E6%89%A7%E8%A1%8C%E7%8E%AF%E5%A2%83

 

谈谈你对原型链的理解?✨

这个问题关键在于两个点,一个是原型对象是什么,另外一个是原型链是如何造成的

#原型对象

绝大部分的函数(少数内建函数除外)都有一个prototype属性,这个属性是原型对象用来建立新对象实例,而全部被建立的对象都会共享原型对象,所以这些对象即可以访问原型对象的属性。

例如hasOwnProperty()方法存在于Obejct原型对象中,它即可以被任何对象当作本身的方法使用.

用法:object.hasOwnProperty( propertyName )

hasOwnProperty()函数的返回值为Boolean类型。若是对象object具备名称为propertyName的属性,则返回true,不然返回false

var person = {
    name: "Messi",
    age: 29,
    profession: "football player"
  };
console.log(person.hasOwnProperty("name")); //true
console.log(person.hasOwnProperty("hasOwnProperty")); //false
console.log(Object.prototype.hasOwnProperty("hasOwnProperty")); //true

由以上代码可知,hasOwnProperty()并不存在于person对象中,可是person依然能够拥有此方法.

因此person对象是如何找到Object对象中的方法的呢?靠的是原型链。]

 

JavaScript的参数是按照什么方式传递的?

基本类型传递方式

因为js中存在复杂类型和基本类型,对于基本类型而言,是按值传递的.

var a = 1;
function test(x) {
  x = 10;
  console.log(x);
}
test(a); 
console.log(a); 

结果是: 10---1

虽然在函数testa被修改,并无有影响到 外部a的值,基本类型是按值传递的.

#复杂类型按引用传递?

咱们将外部a做为一个对象传入test函数.

var a = {
  a: 1,
  b: 2
};
function test(x) {
  x.a = 10;
  console.log(x);
}
test(a); 
console.log(a); 
// { a: 10, b: 2 }
// { a: 10, b: 2 }

能够看到,在函数体内被修改的a对象也同时影响到了外部的a对象,可见复杂类型是按引用传递的.

但是若是再作一个实验:

var a = {
  a: 1,
  b: 2
};
function test(x) {
  x = 10;
  console.log(x);
}
test(a); 
console.log(a); 

结果是: 

  //10

// { a: 1, b: 2 }

外部的a并无被修改,若是是按引用传递的话,因为共享同一个堆内存,a在外部也会表现为10才对. 此时的复杂类型同时表现出了按值传递按引用传递的特性.

#按共享传递

复杂类型之因此会产生这种特性,缘由就是在传递过程当中,对象a先产生了一个副本a,这个副本a并非深克隆获得的副本a,副本a地址一样指向对象a指向的堆内存.

所以在函数体中修改x=10只是修改了副本a,a对象没有变化. 可是若是修改了x.a=10是修改了二者指向的同一堆内存,此时对象a也会受到影响.

有人讲这种特性叫作传递引用,也有一种说法叫作按共享传递

 

简述JavaScript的垃圾回收机制

引用计次

当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。若是同一个值又被赋给另外一个变量,则该值的引用次数加1。相反,若是包含对这个值引用的变量又取得了另一个值,则这个值的引用次数减1

声明一个对象A,每多一个引用,A引用次数+1,每少一个引用,A的引用次数-1

缺点:相互引用的没法消除

标记清除

当变量进入环境(例如,在函数中声明一个变量)时,就将这个变量标记为“进入环境”。内存不能释放。
当变量离开环境时,则将其标记为“离开环境”。释放变量,回收内存。

相似于,函数执行顺序中,各个变量和函数执行在函数调用栈中,此时有标记,当执行完毕以后,退出调用栈,则消除标记,因此垃圾回收机制在必定时间内回收没有标记的变量

浅析javascript调用栈

 ==========================

浏览器如何解析css选择器

 

浏览器会『从右往左』解析CSS选择器。

咱们知道DOM Tree与Style Rules合成为 Render Tree,其实是须要将Style Rules附着到DOM Tree上,所以须要根据选择器提供的信息对DOM Tree进行遍历,才能将样式附着到对应的DOM元素上。

如下这段css为例

.mod-nav h3 span {font-size: 16px;}

咱们对应的DOM Tree 以下

 

 

若从左向右的匹配,过程是:

  1. 从 .mod-nav 开始,遍历子节点 header 和子节点 div
  2. 而后各自向子节点遍历。在右侧 div 的分支中
  3. 最后遍历到叶子节点 a ,发现不符合规则,须要回溯到 ul 节点,再遍历下一个 li-a,一颗DOM树的节点动不动上千,这种效率很低。

若是从右至左的匹配:

  1. 先找到全部的最右节点 span,对于每个 span,向上寻找节点 h3
  2. 由 h3再向上寻找 class="mod-nav" 的节点
  3. 最后找到根元素 html 则结束这个分支的遍历。

后者匹配性能更好,是由于从右向左的匹配在第一步就筛选掉了大量的不符合条件的最右节点(叶子节点);而从左向右的匹配规则的性能都浪费在了失败的查找上面

浏览器重绘与重排的区别?

  • 重排: 部分渲染树(或者整个渲染树)须要从新分析而且节点尺寸须要从新计算,表现为从新生成布局,从新排列元素
  • 重绘: 因为节点的几何属性发生改变或者因为样式发生改变,例如改变元素背景色时,屏幕上的部份内容须要更新,表现为某些元素的外观被改变

『重绘』不必定会出现『重排』,『重排』必然会出现『重绘』

即大小,位置等变化会带来重排;颜色等变化会致使重绘;

如何触发重排和重绘?

任何改变用来构建渲染树的信息都会致使一次重排或重绘:

  • 添加、删除、更新DOM节点
  • 经过display: none隐藏一个DOM节点-触发重排和重绘
  • 经过visibility: hidden隐藏一个DOM节点-只触发重绘,由于没有几何变化
  • 移动或者给页面中的DOM节点添加动画
  • 添加一个样式表,调整样式属性
  • 用户行为,例如调整窗口大小,改变字号,或者滚动。

#如何避免重绘或者重排?

#集中改变样式

咱们每每经过改变class的方式来集中改变样式

 

// 判断是不是黑色系样式
const theme = isDark ? 'dark' : 'light'

// 根据判断来设置不一样的class
ele.setAttribute('className', theme)

使用DocumentFragment

咱们能够经过createDocumentFragment建立一个游离于DOM树以外的节点,而后在此节点上批量操做,最后插入DOM树中,所以只触发一次重排

var fragment = document.createDocumentFragment();

for (let i = 0;i<10;i++){
  let node = document.createElement("p");
  node.innerHTML = i;
  fragment.appendChild(node);
}

document.body.appendChild(fragment);

DOM的事件流是什么

事件冒泡(event bubbling),即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,而后逐级向上传播到较为不具体的节点。

<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<body>
<div></div>
</body>    
</html>

若是单击了页面中的<div>元素,那么这个click事件沿DOM树向上传播,在每一级节点上都会发生,按照以下顺序传播:

  1. div
  2. body
  3. html
  4. document

#事件捕获

事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用意在于在事件到达预约目标以前就捕获它。

仍是以上一节的html结构为例:

在事件捕获过程当中,document对象首先接收到click事件,而后事件沿DOM树依次向下,一直传播到事件的实际目标,即<div>元素

  1. document
  2. html
  3. body
  4. div

事件流又称为事件传播,DOM2级事件规定的事件流包括三个阶段:事件捕获阶段(capture phase)、处于目标阶段(target phase)和事件冒泡阶段(bubbling phase)。

2019-06-23-03-06-09

触发顺序一般为

  1. 进行事件捕获,为截获事件提供了机会
  2. 实际的目标接收到事件
  3. 冒泡阶段,能够在这个阶段对事件作出响应

什么是事件委托

事件委托就是利用事件冒泡,只指定一个事件处理程序,就能够管理某一类型的全部事件.

在绑定大量事件的时候每每选择事件委托。

<ul id="parent">
  <li class="child">one</li>
  <li class="child">two</li>
  <li class="child">three</li>
  ...
</ul>

<script type="text/javascript">
  //父元素
  var dom= document.getElementById('parent');

  //父元素绑定事件,代理子元素的点击事件
  dom.onclick= function(event) {
    var event= event || window.event;
    var curTarget= event.target || event.srcElement;

    if (curTarget.tagName.toLowerCase() == 'li') {
      //事件处理
    }
  }
</script>

优势:

  • 节省内存占用,减小事件注册
  • 新增子对象时无需再次对其绑定事件,适合动态添加元素

局限性:

  • focus、blur 之类的事件自己没有事件冒泡机制,因此没法委托
  • mousemove、mouseout 这样的事件,虽然有事件冒泡,可是只能不断经过位置去计算定位,对性能消耗高,不适合事件委托

vue中央事件总线eventBus的简单理解和使用

 

实现instanceOf

// 模拟 instanceof
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 时,返回 true
      return true;
    L = L.__proto__; //L在上面已经等于了其隐式原型,即父级的显示原型,因此这里至关于L往上走了一级
  }
}

 

js中new一个对象的过程

function Person(name, age) { 
this.name = name; 
this.age = age; 
} 
var person = new Person("Alice", 23); 

new一个对象的四个过程:

一、建立一个空对象  var obj = new Object(); 

二、让Person中的this指向obj,并执行Person的函数体  var result = Person.call(obj); 

三、设置原型链,将obj的__proto__成员指向了Person函数对象的prototype成员对象  obj.__proto__ = Person.prototype; 

四、判断Person的返回值类型,若是是值类型,返回obj。若是是引用类型,就返回这个引用类型的对象。

if (typeof(result) == "object") 
person = result; 
else
person = obj;

 es5和es6实现类的继承    : http://www.javashuo.com/article/p-mcbrfimx-cx.html

 


如何实现一个Event

React/Vue不一样组件之间是怎么通讯的?

首先看一下es6中规定的Map的用法:

1,js建立map对象

var map = new Map();

2.将键值对放入map对象

map.set("key",value)

map.set("key1",value1)

map.set("key2",value2)

3.根据key获取map值

map.get(key)

4.删除map指定对象

delete map[key]

5.循环遍历map

map.

forEach(function(key){
  console.log("key",key)  //输出的是map中的value值

})

------------------

Vue

  1. 父子组件用Props通讯
  2. 非父子组件用Event Bus通讯
  3. 若是项目够复杂,可能须要Vuex等全局状态管理库通讯
  4. $dispatch(已经废除)和$broadcast(已经废除)

React

  1. 父子组件,父->子直接用Props,子->父用callback回调
  2. 非父子组件,用发布订阅模式的Event模块
  3. 项目复杂的话用Redux、Mobx等全局状态管理管库
  4. 用新的Context Api
<!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>Document</title>
</head>

<body>
    <div class="bottom">bottom</div>
</body>
<script>
    class EventEmeitter {
        constructor() {
            this._events = this._events || new Map(); // 储存事件/回调键值对
            this._maxListeners = this._maxListeners || 10; // 设立监听上限
        }
    }
    // 触发名为type的事件
    EventEmeitter.prototype.emit = function (type, ...args) {
        //type--arson
        //...args---low-end
        let handler;
        // 从储存事件键值对的this._events中获取对应事件回调函数
        handler = this._events.get(type);
        //console.log(handler);
        if (args.length > 0) {
            handler.apply(this, args);
        } else {
            handler.call(this);
        }
        return true;
    };

    // 监听名为type的事件
    EventEmeitter.prototype.addListener = function (type, fn) {
        // 将type事件以及对应的fn函数放入this._events中储存
        if (!this._events.get(type)) {
            this._events.set(type, fn);
        }
    };
    const emitter = new EventEmeitter();

    // 监听一个名为arson的事件对应一个回调函数
    emitter.addListener('arson', man => {
        console.log(`expel ${man}`);
    });
    var bottom = document.querySelector('.bottom');
    bottom.addEventListener('click', function () {
        // 咱们触发arson事件,发现回调成功执行
        emitter.emit('arson', 'low-end');
    })
</script>
</html>
相关文章
相关标签/搜索