js之this

为什么要用this

function identify() {
    return this.name
}
let people={
    name:'名字'
}
identify.call(people)//返回 '名字'
function identify(context) {
    return context.name
}
let people={
    name:'名字'
}
identify(people)//返回 '名字'
上面的两段代码执行结果是一样的,但是一个用的this,一个是显式的传入context,
当我们设计API时,或者代码复杂后,显示传入context会让代码混乱起来,

this是什么

this是一个运行时的概念,当一个函数被调用,就会创建一个执行上下文,执行上下文里的 lexical environment:词法环境 里就绑定了this指向(之前的执行上下文博客里也说过),指向什么完全取决于函数在哪里被调用(当前的调用栈

this绑定规则

  • 默认绑定
  • 隐式绑定
  • 显示绑定
  • new绑定

默认绑定

//默认绑定
function foo() {
    console.log(this.a)
}
let a=1
foo()//1
//如此foo()直接使用不带修饰的函数引用进行调用的就会进行默认绑定

但是要注意在严格模式下上面的this会指向undefined

隐式绑定

//隐式调用

function foo() {
    console.log(this.a)
}
let obj2={
    a:1,
    foo:foo
}
let obj1={
    a:2,
    obj2:obj2
}
obj.obj2.foo()//1
//当函数拥有调用者,this就会被指向调用者这个对象

这里要着重说下调用者 下面代码就不会隐式绑定this,因为没有实际调用者,就会进行默认绑定

function foo() {
    console.log(this.a)
}

function active(fn) {
    //fn其实foo的引用,参数传递其实是隐式赋值
    fn(); // 真实调用者,为独立调用
}

var a = 20;
var obj = {
    a: 10,
    getA: foo
}

active(obj.getA);
//

显式绑定 (使用call apply bind 绑定this)

function fn(num1, num2) {
    console.log(this.a + num1 + num2);
}
var obj = {
    a: 20
}

fn.call(obj, 100, 10); // 130
fn.apply(obj, [20, 10]); // 50
fn.bind(obj)

call 和apply 都将函数的this指向第一个参数

call和apply实现结果都是一样的,区别在于后面的参数,都是向将要执行的函数传递参数。其中call以一个一个的形式传递,apply以数组的形式传递。这是他们唯一的不同。

我们可以这样使用:

function turnToArray() {
    var arrArgs = [].slice.call(arguments) // 将参数转换成数组
    //arrArgs=Array.from(arguments)
    console.log(arrArgs) // ['a','b']
}
turnToArray('a', 'b')

new绑定

function Person(name, age) {

    // 这里的this指向了谁?
    this.name = name;
    this.age = age;   
}

Person.prototype.getName = function() {

    // 这里的this又指向了谁?
    return this.name;
}

// 上面的2个this,是同一个吗,他们是否指向了原型对象?

var p1 = new Person('Nick', 20);
p1.getName();
//其实都是指向p1这个实例

通过new操作符调用构造函数,会经历以下4个阶段。

  • 创建一个新的对象;
  • 将构造函数的this指向这个新对象;
  • 指向构造函数的代码,为这个对象添加属性,方法等;
  • 返回新对象。

以上绑定方式的优先级 new>显式绑定>隐式绑定>默认绑定

参考 https://yangbo5207.github.io/wutongluo/ji-chu-jin-jie-xi-lie/wu-3001-this.html ;你不知道的js上卷

css-BFC

先了解盒模型

box-sizing又分为.content-box和border-box,所造成的就是width表示的返回不同

块格式化上下文(Block Formatting Context,BFC) 是Web页面的可视CSS渲染的一部分,是块盒子的布局过程发生的区域,也是浮动元素与其他元素交互的区域

以下方法都可以创建BFC

  • 根元素(<html>)
  • 浮动元素(元素的 float 不是 none
  • 绝对定位元素(元素的 position 为 absolute 或 fixed
  • 行内块元素(元素的 display 为 inline-block
  • 表格单元格(元素的 display为 table-cell,HTML表格单元格默认为该值)
  • 表格标题(元素的 display 为 table-caption,HTML表格标题默认为该值)
  • 匿名表格单元格元素(元素的 display为 table、table-row、 table-row-group、table-header-group、table-footer-group(分别是HTML table、row、tbody、thead、tfoot的默认属性)或 inline-table
  • overflow 值不为 visible 的块元素
  • display 值为 flow-root 的元素
  • contain 值为 layoutcontent或 paint 的元素
  • 弹性元素(display为 flex 或 inline-flex元素的直接子元素)
  • 网格元素(display为 grid 或 inline-grid 元素的直接子元素)
  • 多列容器(元素的 column-count 或 column-width 不为 auto,包括 column-count 为 1
  • column-span 为 all 的元素始终会创建一个新的BFC,即使该元素没有包裹在一个多列容器中(标准变更Chrome bug)。

创建一个会包含这个浮动的BFC,通常的做法是设置父元素 overflow: auto 或者设置其他的非默认的 overflow: visible 的值。

块格式化上下文对浮动定位与清除浮动都很重要。浮动定位和清除浮动时只会应用于同一个BFC内的元素。浮动不会影响其它BFC中元素的布局,而清除浮动只能清除同一BFC中在它前面的元素的浮动。外边距折叠也只会发生在属于同一BFC的块级元素之间。

  • 大家请记住下面这个表现原则:如果一个元素具有 BFC,内部子元素再怎么翻江倒海、翻云覆雨,都不会影响外部的元素。所以,BFC 元素与相邻元素之间是不可能发生 margin 重叠的,因为 margin 重叠是会影响外部的元素的;BFC 元素也可以用来清除浮动的影响,因为如果不清除,子元素浮动则父元素高度塌陷,必然会影响后面元素布局和定位,这显然有违 BFC 元素的子元素不会影响外部元素的设定。
  • block-level 表示可以被放入 bfc
  • block-container 表示可以容纳 bfc
  • block-box = block-level + block-container
  • block-box 如果 overflow 是 visible, 那么就跟父 bfc 合并

除此之外还有IFC(inline-formatting-content) FFC( flex formatting contexts )

参考: https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Flow_Layout/Intro_to_formatting_contexts

toy browser

这篇博客主要是熟悉浏览器的大致工作流程(不涉及代码,代码在最后的链接里)

如上图所示:

  • url回车后,先是解析http
  • 再解析html生成dom树
  • 再解析css
  • 再依据dom和css,进行layout获取position
  • 最后进行render 绘制bitmap

第一步(解析http)

response:

要想解析http 报文,我们可以利用状态机,使用多个状态来区分报文里的每个部分

比如设置一个 WAITTING_STATUS_LINE 状态,在第一个’\n’之前我们都可以视为在 WAITTING_STATUS_LINE 状态,’\n’之后用 WAITTING_STATUS_LINE_END 表示status line 的部分结束,以此类推使用各个状态来筛选响应报文的内容

这样我们就可以拿到response body 里面的html文本

第二步,解析html生成dom树

要想利用html文本最后返回一颗dom树,还是创建状态机来实现,其实html规范里面已经定义了很多很多个状态了,我们只选取一些 状态来模拟一下大致过程,'<‘表示 tagOpen ,在tagOpen之后就是tagName如果后面有个空格后面又有字符串那就是 attributeName 紧跟着’=’,后面就是 attributeValue,简而言之就是分析每一个token,找到每一个token所表达的语义

最后获得多个平级的对象,这个对象如下图结构

接下来就是如何把同级对象构建成一颗dom树

  • 从标签构建DOM树的基本技巧是使用栈
  • 遇到开始标签时创建元素并入栈,遇到结束标签时出栈
  • 自封闭节点可视为入栈后立刻出栈
  • 任何元素的父元素是它入栈前的栈顶

如此操作之后会获得如下图包含children的一个树结构(ast)

第三步 给每个dom树节点匹配对应的css rules

首先利用css库将<style>里的css表达式转换成css rules(其实现方式也是利用状态机生成ast),rule结构如下,declartions里面包含了css属性名和属性值,

然后根据当前的css rule的 selectors以及它的parent 的selectors 和第二步我们获得dom树进行比对,得到当前的css rule 应该添加到哪一个元素上,把这些css rule 添加到元素的computedStyle属性上

第四步 进行layout 计算每个元素在坐标轴上的位置

首先我们需要抽象出一个坐标轴

主轴:mainSize, mainStart, mainEnd, mainSign, mainBase,       

交叉轴:  crossSize, crossStart, crossEnd, crossSign, crossBase

例如flex布局中的 flexDirection === ‘row’ ,那么上面的变量就是如此:

        mainSize = 'width';
        mainStart = 'left';
        mainEnd = 'right';
        mainSign = +1;
        mainBase = 0;

        crossSize = 'height';
        crossStart = 'top';
        crossEnd = 'bottom'

然后把每个元素放入一行(行可以用数组实现)根据主轴的尺寸和css规则判断换不换行,再计算交叉轴的高度(取每一行的元素的最高交叉轴尺寸),获得每个元素在坐标轴上的坐标表示,如element[mainSize],element[crossSize]的具体数值

第五步根据坐标点render进行绘制

这里使用images库( Node.js轻量级跨平台图像编解码库 ),把上面的元素传入images的绘制方法就可以了,循环调用子元素的绘制方法完成dom树的 绘制(这里没有实现文字的绘制和图层的一些处理)

完整的代码地址: https://github.com/dz333333/Frontend-01-Template/tree/master/week07/layout