Code Monkey home page Code Monkey logo

blog's Introduction

前端大马猴👋

🛠 Tech Stack

JavaScript TypeScript Vue.js React Node.js Visual%20Studio%20Code&logoColor

* 鸡你太美守护团🐔

blog's People

Contributors

g-zhima avatar

Watchers

 avatar

blog's Issues

JS 传递参数,是按值传递还是引用传递?

首先明确答案,JavaScript 中所有函数的参数都是按值传递的!

传递基本类型的数据比较好理解,被传递的值直接复制给命名参数。

function add(num) {
    num += 10;
    return num;
}

let count = 20;
let result = add(count);
alert(count);	//20
alert(result);	//30

调用函数时,count 将值复制给函数中的参数 num。在函数中 num 被加上了 10,但是并不会影响 count 的值,它们存着数值相同但相互独立的值。如果 num 是按引用传递的话,count 的值将变为30,反映函数内部的变化。

当传递的参数是引用类型的数据时,函数参数仍然是按值传递。

function setName(obj) {
	obj.name = 'zhima';
}

let person = {};
setName(person);
alert(person.name);	//'zhima'

首相我们创建了一个名为 person 的空对象,也就是说:person存着空对象的引用。当调用函数时,person 将它存的值(也就是空对象的引用)复制给 obj 。所以 personobj 引用是同一个变量,函数内部的修改,会反映到函数外来。

这里要注意区分访问变量的方式和传递参数的方式不是一回事。person 是按值传递给 obj 的,但是 personobj 都是按引用来访问同一个对象的,因为 person 中的值就是对象的引用。

Flex布局

Flex 布局是什么?

Flex 是 Flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性。任何一个容器都可以指定为 Flex 布局,行内元素也可以使用 Flex 布局。

注意,设为 Flex 布局以后,子元素的floatclearvertical-align属性将失效。


Flex 基本概念

采用 Flex 布局的元素,称为 Flex 容器(flex container),简称"容器"。它的所有子元素自动成为容器成员,称为 Flex 项目(flex item),简称"项目"。

基本概念

容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)。主轴的开始位置(与边框的交叉点)叫做main start,结束位置叫做main end;交叉轴的开始位置叫做cross start,结束位置叫做cross end。

项目默认沿主轴排列。单个项目占据的主轴空间叫做main size,占据的交叉轴空间叫做cross size。


容器属性

  • flex-direction
  • flex-wrap
  • flex-flow
  • justify-content
  • align-items
  • align-content

flex-direction

flex-direction 决定主轴的方向

  • row(默认值):主轴为水平方向,起点在左端。
  • row-reverse:主轴为水平方向,起点在右端。
  • column:主轴为垂直方向,起点在上沿。
  • column-reverse:主轴为垂直方向,起点在下沿。

direction


flex-wrap

flex-wrap 属性定义,如果一条轴线排不下,如何换行。

  • nowrap(默认):不换行。
  • wrap:换行,第一行在上方。
  • wrap-reverse:换行,第一行在下方。

wrap


flex-flow

flex-flow 属性是 flex-direction 属性和 flex-wrap 属性的简写形式,默认值为 row nowrap


justify-content

justify-content 属性定义了项目在主轴上的对齐方式。

  • flex-start(默认值):左对齐(假设主轴为从左到右,下同)
  • flex-end:右对齐
  • center: 居中
  • space-between:两端对齐,项目之间的间隔都相等。
  • space-around:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍。

justify-content


align-items

align-items 属性定义项目在交叉轴上如何对齐。

  • flex-start:交叉轴的起点对齐。(假设交叉轴从上到下,下同)
  • flex-end:交叉轴的终点对齐。
  • center:交叉轴的中点对齐。
  • baseline: 项目的第一行文字的基线对齐。
  • stretch(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。

aligin-item


align-content

align-content 属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。

  • flex-start:与交叉轴的起点对齐。
  • flex-end:与交叉轴的终点对齐。
  • center:与交叉轴的中点对齐。
  • space-between:与交叉轴两端对齐,轴线之间的间隔平均分布。
  • space-around:每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。
  • stretch(默认值):轴线占满整个交叉轴。

项目属性

  • order
  • flex-grow
  • flex-shrink
  • flex-basis
  • flex
  • align-self

order

order 属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。


flex-grow

flex-grow 属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。

用来“瓜分”父项的“剩余空间”。如果所有项目 flex-grow 都为 1,将等比例沾满容器。


flex-shrink

flex-shrink 属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。

用来“吸收”子项“超出的空间”。如果所有项目的 flex-shrink 属性都为 1,当空间不足时,都将等比例缩小。如果一个项目的 flex-shrink 属性为 0,其他项目都为 1,则空间不足时,前者不缩小。


flex-basis

flex-basis 属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。

flex-basis 如果设置了值,则子项占用的空间为设置的值;如果没设置或者为 auto,那子项的空间为 width/height 的值。


flex

flex 属性是 flex-grow, flex-shrinkflex-basis 的简写,默认值为 0 1 auto。后两个属性可选。

有两个快捷值:auto1 1 auto) 和 none0 0 auto


aligin-item

aligin-item 属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。

从JavaScript的角度理解Java的lambda表达式和方法引用

最近学习Java第一次看到lambda表达式和方法引用时有些懵逼,OOP的Java突然混入函数式着实让我不好理解,不过我发现如果从JavaScript的角度去理解这两个特性就丝滑的多了。

9150e4e5jw1fckf1k2b32j205i05i0sp


首先Java中的lambda表达式其实和JavaScript中的箭头函数类似,都是将函数作为参数传入另一个函数。JavaScript中这很自然,因为函数在JS中就是值,它和数字,字符串没什么区别。但是放在Java中问题来了,Java的函数不是值(或者说不是对象),另一个函数在参数中没法接收一个不是值的结构。所以Java引入了函数式接口的概念,这类接口只有一个抽象方法可以去实现,由实现了该类接口的类的实例对象去模拟要作为参数传入的函数。

下面定义了一个需要接收Consumer实现类实例的函数,这里的Consumer就是一个函数式接口,它有一个accept抽象方法。

public void fn(String str, Consumer<String> consumer) {
    consumer.accept(str);
}

现在把该函数的第二个参数想象成JS中的的回调函数,它要接收的就是一个函数对象。

function fn(str, callback) {
    callback(str)
}

好了,定义了需要接收函数作为参数的高阶函数,要怎么使用这个高阶函数呢?

Java中最自然想到使用方式就是传入实现了该Consumer接口类的实例对象了。但是这种实现太不“函数式”了。

fn("zhima", new Consumer<String>() {
    @Override
    public void accept(String s) {
        // xxxx
    }
});

于是就引入了lambda表达式,根本上讲它就是上面方式的语法糖,但是从结果上讲,它看起来确实足够“函数式”。

fn("zhima", str -> {
    // xxxx
});

再看看JS的实现方式,不说说是极为相似简直是一模一样。

fn("zhima", str => {
    // xxxx
})

函数调用模式中的 this 和 that

在 JS 中函数有 4 种调用模式:方法调用模式、函数调用模式、构造器调用模式、apply 调用模式。这些模式在初始化 this 上存在差异。

方法调用模式中的 this 绑定到调用它的对象上。

构造器调用模式中的 this 绑定在通过该构造函数新创建的新对象上。

apply 调用模式中的 this 绑定在传给 apple/call 方法第一个参数指向的对象上。

函数调用模式有些特殊,当以此方式调用函数时,this 被绑定到了全局对象上。这是语言设计的错误,导致方法不能利用内部的函数来帮助它工作,因为内部函数的 this 被绑定在了全局上,所以不能共享该方法对对象的访问权

let o = {
	f1: function () {
		console.log(this);	
		let f2 = function () {
			console.log(this);
		};
		f2();	//函数调用模式,this 值绑定到了 window。
	}
};
o.f1()	//对象的方法调用,this 的值是对象 o。

上面代码包含两层this,结果运行后,第一层指向该对象,第二层指向全局对象。一个解决方法是在第二层改用一个指向外层this的变量。

let o = {
	f1: function () {
		console.log(this);
         var that = this;
		let f2 = function () {
			console.log(that);
		};
		f2();
	}
};
o.f1()

上面代码定义了变量that,固定指向外层的this,然后在内层使用that,就不会发生this指向的改变。

css选择器

css选择器

元素选择器

通配选择器

* {
  样式名:样式值;
}

标签选择器

tag {
	样式名:样式值;
}

类型选择器

.class {
	样式名:样式值;
}

ID选择器

#id {
	样式名:样式值;
}

关系选择器

包含选择器

E F {
	样式名:样式值;
}

子选择器不同的是,包含选择符将会命中所有符合条件的后代,包括儿子,孙子,孙子的孙子...

自选择器

E>F {
	样式名:样式值;
}

包含选择符不同的是,子选择符只能命中子元素,而不能命中孙辈。

相邻选择器

E+F {
	样式名:样式值;
}

选择紧贴在 E 元素之后 F 元素,元素 E 与 F 必须同属一个父级。与兄弟选择器相同的是,相邻选择符也是选择同级的元素 F;不同的是,相邻选择符只会命中符合条件的那个毗邻的兄弟元素(即紧挨着 E 元素之后的第一个F元素)

兄弟选择器

E~F {
	样式名:样式值;
}

选择 E 元素后面的所有兄弟元素 F,元素 E 与 F 必须同属一个父级。

属性选择器

[att]

选择具有 att 属性的元素

例如:

<style>
img[alt] {
	margin: 10px;
}
</style>

<img src="图片url" alt="" /> <!-- 选中 -->
<img src="图片url" />

[att="val"]

选择具有 att 属性且属性值等于 val 的元素。

例如:

<style>
input[type="text"] {
	border: 2px solid #000;
}
</style>

<input type="text" /> <!-- 选中 -->
<input type="submit" />

[att~="val"]

选择具有 att 属性且属性值为一用空格分隔的字词列表,其中一个等于 val 的元素(包含只有一个值且该值等于 val 的情况)。

例如:

<style>
div[class~="a"] {
	border: 2px solid #000;
}
</style>

<div class="a">1</div> <!-- 选中 -->
<div class="b">2</div>
<div class="a b">3</div> <!-- 选中 -->

[att^="val"]

选择具有 att 属性且属性值为以 val 开头的字符串的元素。

例如:

<style>
div[class^="a"] {
	border: 2px solid #000;
}
</style>

<div class="abc">1</div> <!-- 选中 -->
<div class="acb">2</div> <!-- 选中 -->
<div class="bac">3</div>

[att$="val"]

选择具有 att 属性且属性值为以 val 结尾的字符串的元素。

例如:

<style>
div[class$="c"] {
	border: 2px solid #000;
}
</style>

<div class="abc">1</div> <!-- 选中 -->
<div class="acb">2</div>
<div class="bac">3</div> <!-- 选中 -->

[att*="val"]

选择具有 att 属性且属性值为包含 val 的字符串的元素。

例如:

<style>
div[class*="b"] {
	border: 2px solid #000;
}
</style>

<div class="abc">1</div> <!-- 选中 -->
<div class="acb">2</div> <!-- 选中 -->
<div class="bac">3</div> <!-- 选中 -->

[att|="val"]

选择具有 att 属性且属性值以 val- 开头的或值为 val 的元素。

例如:

<style>
div[class|="a"] {
	border: 2px solid #000;
}
</style>

<div class="a">0</div> <!-- 选中 -->
<div class="a-test">1</div> <!-- 选中 -->
<div class="b-test">2</div>
<div class="c-test">3</div>

伪类选择器

:link

设置超链接a在未被访问前的样式。

:visited

设置超链接a在其链接地址已被访问过时的样式。

:hover

设置元素在其鼠标悬停时的样式。

:active

设置元素在被用户激活(在鼠标点击与释放之间发生的事件)时的样式。

注意:超链接的4种状态,需要有特定的书写顺序才能生效。

a:link {}
a:visited {}
a:hover {}
a:active {}

:first-child

匹配父元素的第一个子元素。

注意:

<style>
	li:first-child{} /* 可以选中第一个 li 元素 */
  ul:first-child{} /* 不可以选中第一个 li 元素 */
</style>
<ul>
	<li>列表项一</li>
	<li>列表项二</li>
	<li>列表项三</li>
	<li>列表项四</li>
</ul>

:last-child

匹配父元素的最后一个子元素。

:nth-child(n)

匹配父元素的第 n 个子元素。

注意:该选择符允许使用一些关键字,比如:odd, even

<style>
li:nth-child(even){color:#f00;} /* 偶数 */
li:nth-child(odd){color:#000;} /* 奇数 */
</style>

<ul>
	<li>列表项一</li>
	<li>列表项二</li>
	<li>列表项三</li>
	<li>列表项四</li>
</ul>

伪元素选择器

::first-letter

设置对象内的第一个字符的样式。

::first-line

设置对象内的第一行的样式。

::before

设置在对象前的内容。用来和 content 属性一起使用,并且必须定义 content 属性。

注意:伪元素是行内级元素

::after

设置在对象后的内容。用来和 content 属性一起使用,并且必须定义 content 属性。

解读Babel常用配置

Babel 的工作原理

在 Babel 对 ES6 代码进行转换时,分为两个部分:一个是语法部分,另一个是 API 部分。

Babel 对语法部分的转译过程主要分为三个阶段:parsing(解析)、transforming(转化)、generating(生成)

源代码 --> AST --> 转化 --> 新 AST --> 目标代码

Babel 对 API 部分相对简单,通过引入 Polyfill 来抹平 API 差异。

Babel 的使用

基本安装

要使用 Babel 首先应该安装@babel/core,它是 Babel 的核心模块。另外如果希望通过终端对源代码进行编译,还需要安装@babel/cli,它提供了 Babel 命令,并可以通过命令行设置 Babel 的相关配置。

初步使用

初始化项目,并将@babel/core@babel/cli安装为开发依赖后,就可以尝试进行一个最简单的转译工作了:

首先编写 ES6+ 的源代码 index.js :

const foo = () => {
  console.log('foo')
}

之后打开终端运行npx babel index,js -o dist ,指示 Babel 将 index.js 转译并输出到 dist 目录下。

打开 dist 目录下转译好的文件,可以发现输出的文件并没有做任何的转译......这是因为 Babel 本身并没有转化的功能,它将转化的功能提取到不同的 plugin 中,供我们单独选择。

安装插件

既然转化功能在插件中,那我们就需要安装对应的插件。上述的 index.js 中用到了两处 ES6+ 语法:const 和箭头函数,所对应的插件分别是@babel/plugin-transform-block-scoped-functions@babel/plugin-transform-arrow-functions

安装完成后,重新在终端执行npx babel index,js -o dist --plugin=xxx,指示 Babel使用特定的 plugin 对文件进行转译。

查看输出的文件,果然被转译为了 ES5 的代码,但在使用中你一定发现一个问题,ES6 的新特性那么多,总不能每次都下载名字这么长的 plugin。

安装预设

有了问题自然有解决的方案,Babel 对上面问题的解决方案就是 preset,一个特定的 preset 包含了一组不同功能的 plugin,使用最多的 preset 就是@babel/preset-env,该预设包括了所有 ES6+ 标准的新特性,极大的减少了配置插件的复杂。

安装对应的 preset,在终端执行npx babel index,js -o dist --preset=@babel/preset-env,指示 Babel 使用特定预设进行转义。

观察输出文件,和使用 plugin 转译的结果没有区别,证明 preset 应用正常。

配置文件

通过上诉的步骤,我们已经把 ES6+ 代码转译到了 ES5,但是每次在命令行输入各种配置仍然不够优雅,且如果有大量的配置需要调整,这个工作量是极大的,那怎么把配置提取出来呢?可以使用 Babel 的配置文件对 Babel 的工作进行设定。

在项目根目录新建 babel.config.json 文件,并且写入:

{    
  "presets": ["@babel/preset-env"]
}

下次执行命令时,Babel 会自动应用该配置文件的配置。

Polyfill

再次新建一个 ES6+ 源码 promise.js:

const p = new Promise((resolve, reject) => {    
  resolve(100);
});

通过转移后输出:

"use strict";

var p = new Promise(function (resolve, reject) {
  resolve(100);
});

语法被正常转译,但似乎又出现了一个新问题,promise 对象是 ES6 新加入的对象,Babel 似乎并没有将它转换。这是因为 promise 属于API 层级的新特性,而 Babel 默认只会转化语法层次的特性。要解决这个问题就要引入 Polyfill,polyfill 的中文意思是垫片,顾名思义就是垫平不同浏览器或者不同环境下的差异,让新的内置函数、实例方法等在低版本浏览器中也可以使用。

安装 @babel/polyfill 依赖,要注意这是一个运行时依赖。然后在 promise .js 中引入:

import '@babel/polyfill';
const p = new Promise((resolve, reject) => {    
  resolve(100);
});

转译结果:

"use strict";

require("@babel/polyfill");

var p = new Promise(function (resolve, reject) {
  resolve(100);
});

可以我们引入的 polyfill 中已经包含了对Promise的es5的定义,所以这时候代码便可以在低版本浏览器中运行了。

优化 Babel 配置

useBuiltIns属性

不知道大家又没有想到这样一个问题,代码里边只用到了几个新特性,却需要引入所有的垫片,这不合情理啊。要优化这一点,就需要用到 useBuiltIns 这个属性了。

修改 Babel 配置文件:

{
  "presets": [
    ["@babel/preset-env",{
      "useBuiltIns": "usage",
      "corejs":3
    }]
  ]
}

并且由于直接使用 @babel/polyfill 的缺点,官方已经将其弃用,我们可以直接删去对其的引用。

重新转译,再次查看转译文件:

"use strict";

require("core-js/modules/es.object.to-string");

require("core-js/modules/es.promise");

var p = new Promise(function (resolve, reject) {
  resolve(100);
});

通过给 @babel/env 配置 useBuiltInscorejs属性,我们实现了 polyfill 方法的按需加载。

@babel/plugin-transform-runtime

重新建立一个新的源代码文件 Person.js:

class Person {
  constructor(name) {
    this.name = name;
  }
  say() {
    console.log(this.name);
  }
}

查看转译结果:

"use strict";

require("core-js/modules/es.function.name");

require("core-js/modules/es.object.define-property");

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

var Person = /*#__PURE__*/function () {
  function Person(name) {
    _classCallCheck(this, Person);

    this.name = name;
  }

  _createClass(Person, [{
    key: "say",
    value: function say() {
      console.log(this.name);
    }
  }]);

  return Person;
}();

发现什么问题了吗,代码中出现了大量的辅助函数,如果有大量的源代码文件使用了 ES6+ 的新特性,每个文件中都会重复的出现这些辅助函数,这造成了很多的代码冗余,不利于应用的体积缩小。当使用了 plugin-transform-runtime 插件后,就可以将Babel 转译时添加到文件中的内联辅助函数统一隔离到 babel-runtime 提供的 helper 模块中,编译时,直接从 helper 模块加载,不在每个文件中重复的定义辅助函数,从而减少包的尺寸。

安装 @babel/plugin-transform-runtime@babel/runtime,并在 Babel 的配置文件中添加:

{
  "plugins": ["@babel/plugin-transform-runtime"]
}

重新转译,查看输出的结果:

"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

require("core-js/modules/es.function.name");

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));

var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));

var Person = /*#__PURE__*/function () {
  function Person(name) {
    (0, _classCallCheck2["default"])(this, Person);
    this.name = name;
  }

  (0, _createClass2["default"])(Person, [{
    key: "say",
    value: function say() {
      console.log(this.name);
    }
  }]);
  return Person;
}();

@babel/plugin-transform-runtime除了可以解决辅助函数冗余的问题外,还能解决 polyfill 对全局环境的污染。

回头看 promise.js 的转译结果,其中在顶部有这两行代码:

require("core-js/modules/es.object.to-string");

require("core-js/modules/es.promise");

可以看出在处理例如 Promise 这种的 api 时,只是引入了 core-js 中的相关的库,这些库重新定义了 Promise,然后将其挂载到了全局。

这里的问题就是:必然会造成全局变量污染,同理其他的例如 Array.from 等会修改这些全局对象的原型 prototype,这也会造成全局对象的污染。

解决方式就是:将 core-js 交给 transform-runtime 处理。修改 Babel 配置文件:

{
  "presets": ["@babel/env"],
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "corejs": { "version": 3 }
      }
    ]
  ]
};

重新转译后,查看结果:

"use strict";

var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");

var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));

new _promise["default"](function (resolve, reject) {
  resolve(100);
});

可以看出,这时的代码并没有在全局直接添加一个Promse,而是定义了一个_promise["default"]方法,这样便不会出现全局变量污染的情况。

HTML5标签嵌套问题

一直认为 HTML 标签嵌套的问题很简单,直到有同学问我 <p> 标签和 <div> 标签嵌套的问题后,我才发现原来标签嵌套也有这么多的细节,这篇博客就总结一下查到的东西。

首先是之前一直学的嵌套规则:

  • 块级元素可以嵌套块级元素和行内元素。
  • 行内元素可以嵌套行内元素但不能嵌套块级元素。

乍一看没什么问题,但实际网页中似乎有违背这些规则的情况出现。

首先是同为块级元素的 <p> 标签和 <div> 标签嵌套问题,在 <p> 中嵌套 <div> 没有违背嵌套规则,但在浏览器解析时会出现严重的问题:会直接将 <div> 标签包裹在两个 <p> 标签之间。这没有合适的解释,大家都按特殊规则进行记忆。

另一个情况就是会经常看到 <a> 标签中嵌套 <div> 等块级标签,这显然也违背了行内元素不能嵌套块级标签的规则,但事实上很多网站都用了这样的规则。

于是,有事上 google,我发现之前学过的嵌套规则其实属于淘汰后的规则,新的 HTML5 嵌套规则有了很大的区别。

之前的规则把元素分成了块级和行内级,这其实是基于 CSS 样式的分类方式,而 HTML5 新的规范其实一直想强调语义并弱化样式,这样有助于结构和样式的分离,这在 HTML5 中删除了许多定义样式的标签和属性,新增了无样式的语义标签也可以看出。

那新的嵌套规则是什么呢?且听我慢慢道来~~

在新的规范中,标签最常见的有 7 类,并且每种标签可以有多个类型。

  • 元数据内容(Metadata content)
  • 流式元素(Flow content)
  • 章节元素(Sectioning content)
  • 标题元素(Heading content)
  • 短语/段落元素(Phrasing content)
  • 嵌入元素(Embedded content)
  • 交互元素(Interactive content)

具体每个分类中有哪些标签,我就懒得写了,丢个 MDN 链接自行查找。

可以看出新规范的分类方法更加重视了语义,这与 HTML 本身要表达的意思也更符合。每种分类都有相对应的嵌套规则,下面来看几个代表性强的例子:

首先是 <p> 标签,查看 MDN 可知它属于 Flow content,palpable content。它接受的内容为 Phrasing content。而 Phrasing content 中并没有 <div> 标签,所以按规则 <p> 标签是不能嵌套 <div> 标签的。

然后看看 <div> 标签,它允许的子元素是 Flow content,而流式元素基本涵括了页面中的大部分元素,所以我们在用

时可以不用担心嵌套错误的问题。

<a> 标签呢?它稍微特殊一点,它允许的子元素中有个 Transparent,所以它接受的子元素是以它父元素接受的元素为准的,但不能包含交互式元素。所以在新规范下就很明确了,<a> 标签是可以嵌套 <div> 标签的,只要 <a> 标签的父元素可以嵌套 <div> 就行。

最后,我们可以把嵌套规则分成:严格嵌套约束、语义嵌套约束。严格的嵌套约束是必须遵守的,不然会导致浏览器的解析错误,比如 <p> 标签中嵌套 <div> 标签,<a> 标签中嵌套 <a>标签。语义嵌套相对没那么严格,浏览器也会正确解析,但在实际开发还是要尽量遵守,保证语义正确。

JS 对象属性的类型

JS 对象的属性分为两种:数据属性访问器属性

数据属性

数据属性有 4 个描述行为的特性。

  • [[Configurable]] :表示能否配置该属性(修改该属性的其他特性、删除该属性以及将该属性的类型修改为访问器属性)。
  • [[Emumerable]] :表示能否通过 for...in 返回该属性。
  • [[Writable]] :表示能否修改属性的值。
  • Value :表示这个属性的值。读取属性值时,从这个位置读,写入属性值时从这个位置把新值保存在这个位置。

当我们不显式的修改这些特性定义对象的属性时,除 [[Value]] 外,其它特性都为 true

要修改属性的默认特性,需要使用 Object.defineProperty() 方法。

语法是:

Object.defineProperty(obj, propertyName, descriptor)

objpropertyName:要处理的对象和属性。

descriptor:描述符对象。

let user = {};
Object.defineProperty(user, 'name', {
	value: 'zhima'
});

以上我们在对象 user 中创建一个 name 属性,需要注意第二个参数 'name' 是字符串(对象的属性名其实是字符串)。在显式的使用该方法创建新属性时,如果不指定 [[Configurable]] [[Emumerable]] [[Writable]] 都将是 false

现在让我们通过示例来看看标志的效果。

只读

我们通过修改 writable 标志来把 user.name 设置为只读:

let user = {
  name: "zhima"
};

Object.defineProperty(user, "name", {
  writable: false
});

user.name = "Pete";	// 错误,user 的name 值不会改变。

现在没有人可以改变我们的用户名称,除非他重新调用 defineProperty 来覆盖我们的用户。

不可枚举

我们可以设置 enumerable:false。然后它不会出现在 for..in 循环中:

let user = {
  name: "zhima",
  sayHi() {
    console.log('hi')
  }
};

Object.defineProperty(user, "sayHi", {
  enumerable: false
});

for (let key in user) alert(key); // name

不可配置

我们可以设置 Configurable: false,一个不可配置的属性不能被 defineProperty 删除或修改。

注意:当 Configurable: false 时,[[Writable]] 特性可以单向从 true 修改为 false ,而 [[Value]] 特性只与 [[Writable]] 特性关联不受 [[Configurable]] 影响。

let user = {
    name: 'zhima'
};

Object.defineProperty(user, "name", {
  configurable: false	// 因为 [[Writable]] 为 true,所以可以通过修改 [[Value]] 来修改属性的值
});
let user = {
    name: 'zhima'
};

Object.defineProperty(user, "name", {
  writable: false,	
  configurable: false	// 在这里,我们将 user.name 设置为“永久封闭”的常量:
});

访问器属性

访问器属有 4 个描述行为的特性。

  • [[Configurable]]:同数据属性相似。
  • [[Emumerable]]:同数据属性相似。
  • [[Get]]:在读取属性时调用的函数。
  • [[Set]]:在写入属性时调用的函数。
let user = {
  firstName: "zhima",
  lastName: "xiaoguan",

  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  },
  set fullName(value) {
  	[this.firstName, this.lastName] = value.split(" ");
  }
};

alert(user.fullName); // zhima xiaoguan
user.fullName = 'zhima daguan';
user.firstName = 'zhima';
user.lastName = 'daguan';

Vue 中的计算属性就是访问器属性。

JS 是万物皆对象吗?

常常听到别人说 JS 中万物皆对象,但是事实确实是这样的吗?在高程中我找到了答案。

首先 JS 中有两大类数据类型,基本数据类型引用数据类型,引用数据类型是对象这个不用解释,那基本数据类型的本质也是继承自 Object 嘛?答案是否定的。

首先,基本数据类型是无法添加属性和方法的,而引用类型却可以。

let num = 10;
num.name = 'zhima';
console.log(num.name);	// undefined

let obj = {};
obj.name = 'zhima';
console.log(obj.name);	// 'zhima'

在内存中,基本类型和引用类型的储存方式也是不同的。基本类型的数据直接放在栈内存中,而引用数据类型在栈内存只保存它的引用(指针),真正的值存在堆内存中。这也是这两种数据类型访问方式不同的原因。

所以 JS 万物皆对象的说法是不正确的,但是又为什么常有人造成这样的误解呢?

因为 JS 中基本数据类型(String Boolean Number)也有属于它们的属性和方法,而属性和方法是对象才有的。

最简单的例子:

let s1 = 'zhima';
let s2 = s1.substring(2);

这个例子中,我使用了字符串 s1 的方法,这和基本类型不是对象的说法矛盾了,这就要提到 JS 中的基本包装类型了。

每当我们去调用基本数据类型的属性方法时,JS 都会在后台创建一个对应基本包装类型的对象,从而让我们可以调用一些方法来操作数据。

还是之前的例子,当我们调用 s1 的方法时,JS 在后台完成了一系列的动作:

  1. 创建 String 类型的一个实例;
  2. 在实例上调用指定的方法;
  3. 销毁这个实例。

可以想象为以下三行代码:

let s1 = new String('zhima');
let s2 = s1.substring(2);
s1 = null;

所以引用类型和基本包装类型的区别就在于对象的生命周期,基本包装类型的生命周期很短,只在代码执行的孙坚存在,然后就被销毁了,所以这也是为什么我们不能为基本类型添加属性和方法。

所以这也是以下例子的原因:

var str1 = "zhima";
var str2 = new String("xiaoguan");

console.log(str1.__proto__ === str2.__proto__);	// true
console.log(str1 instanceof String);	// false
console.log(str2 instanceof String);	//true

现在可以说明 JS 并不是万物皆对象了!

React事件中的this绑定及参数传递

React事件的this绑定和参数传递一直都是困扰许多刚学习React新人的一个难题,其实如果JavaScript基础知识扎实,这些概念其实很好理解,在这里详细的说明一下。

首先看this绑定:

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // 为了在回调中使用 `this`,这个绑定是必不可少的
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(state => ({
      isToggleOn: !state.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);

借用React文档的示例,该组件在handleClick函数中使用了this值,而该函数在之后并不由我们主动调用,而是由React在我们点击按钮后回调该函数,所以要对该函数的this进行绑定,确保函数中的this还能正确的指向当前组件的实例对象。上面的代码是其中一个方法,它在类的构造函数中使用bind方法进行显式绑定,而在构造方法中我们可以保证其中的this指向当组件实例,之后又将绑定this后的函数重新赋值给handleClick保证我们之后使用的函数是显示绑定过this值的函数。

上面的方法在使用上略显啰嗦,在每次声明方法后,还要在构造函数进行绑定,所以一个更加方便的方法是使用类字段:

class LoggingButton extends React.Component {
  // 此语法确保 `handleClick` 内的 `this` 已被绑定。
  handleClick = () => {
    console.log('this is:', this);
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}

还是借用React的文档示例来进行说明,这次在声明回调函数时,使用了类字段的方式,将一个匿名箭头函数初始化给handleClick 字段,这确保了该函数中的this已经自动绑定为了当前组件实例。那好奇的小朋友一定会问为什么写成类字段就可以自动绑定this呢?这里我使用一下TypeScript的编译器将上诉代码进行编译,将该语法换成普通的实现更有助于理解。

"use strict";
var LoggingButton = /** @class */ (function () {
  function LoggingButton() {
      this.handleClick =  () => {
          console.log('this is:', this);
      };
  }
  // ...
  return LoggingButton;
}());

可以看到被声明为类字段的函数直接写在构造函数中进行初始化,箭头函数中没有this,它会在寻找上层函数中的this定义,而构造函数中的this在上面已经提到是可以保证为当前组件实例的。

最后还可以直接在JSX中直接使用箭头函数:

class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    // 此语法确保 `handleClick` 内的 `this` 已被绑定。
    return (
      <button onClick={() => this.handleClick()}>
        Click me
      </button>
    );
  }
}

JSX中onClick需要接收一个函数用来在之后被回调,所以我们可以直接给其传递一个匿名的箭头函数,在该函数内调用我们定义好的回调方法。由于箭头函数中没有this,所以它会取render函数中的this,而render函数一定是由当前组件实例调用的,可以保证其中的this值正确指向。

瀑布流布局的简单实现

瀑布流布局是一种网站页面布局方式。视觉表现为参差不齐的多栏布局,主要应用在展示不同大小图片的网站,采用此布局的网站有 Pinterest,花瓣网等。

瀑布流布局效果(花瓣网)

2021-10-11_13-54-28

瀑布流布局实现

基本布局规则

要想实现瀑布流布局,需要对瀑布流布局的基本规则有所了解。

首先瀑布流中所有元素的宽度为定值,即容器宽度除以列数(为简化说明暂时省略列间隙),高度根据元素的比例自适应。水平排列第一行元素后,效果如图所示:

2021-10-11_14-11-04

这里要注意图上的红色线条,它表示了当前布局垂直方向上最短的一项,当有下一项插入时,应将其放入垂直方向最短一项的下方,效果如图所示:

2021-10-11_14-15-19

此时垂直方向上的最短项发生了改变,下一张图片应放入更新后的最短项下放,效果如图所示:

2021-10-11_14-18-07

之后按照该规则依次插入元素,即可实现瀑布流布局。

具体代码实现思路

首先完成 HTML 结构:

<div class="water-fall">
  <div class="water-fall-item"><img src="./imgs/1.webp" alt="" /></div>
  <div class="water-fall-item"><img src="./imgs/2.webp" alt="" /></div>
  <div class="water-fall-item"><img src="./imgs/3.webp" alt="" /></div>
  <div class="water-fall-item"><img src="./imgs/4.webp" alt="" /></div>
  <div class="water-fall-item"><img src="./imgs/5.webp" alt="" /></div>
  <div class="water-fall-item"><img src="./imgs/6.webp" alt="" /></div>
  <!-- more items... -->
</div>

之后设置一下容器宽度,并设置图片宽高:

.water-fall {
  width: 1200px;
  margin: 0 auto;
  position: relative; /* 方便子元素做定位布局的参考 */
}
 
.water-fall-item img {
  width: 100%;
  height: 100%; 
}

接下来就是对子元素布局位置的动态设置了:

首先需要明确瀑布流布局中最关键的两个参数:列数(columns)和列间隙(gap),所以可以编写一个 wateFall 类,并在构造函数中初始化这两项参数。其次需要确定应用该布局的具体容器元素(el):

class wateFall {
  constructor(options) {
    this.gap = options.gap
    this.columns = options.columns
    this.el = options.el
    this.init() // init 函数
  }
}

之后实现 init 函数,来计算容器的宽度,以及所有的子元素列表:

class wateFall {
  // ...
  init() {
    this.elWidth = this.el.offsetWidth
    this.items = this.el.children
    this.setItemWidth() // 设置子元素宽度
    this.render() // 设置子元素在布局中的具体位置
  }
}

对所有子元素的宽度做设置,并将其设置为绝对定位,方便之后对子元素位置做设置:

class wateFall {
  // ...
  setItemWidth() {
    const itemWidth = (this.itemWidth =
      this.elWidth / this.columns - 2 * this.gap)

    Array.from(this.items).forEach((item) => {
      item.style.width = itemWidth + 'px'
      item.style.position = 'absolute'
    })
  }
}

对每一个元素的位置进行计算,并通过 topleft 值进行设置:

class wateFall {
  // ...
  render() {
    const itemsHeigth = []
    Array.from(this.items).forEach((item, index) => {
      if (index < this.columns) {
        item.style.top = '0'
        item.style.left = (this.itemWidth + this.gap * 2) * index + 'px'
        itemsHeigth.push(item.offsetHeight)
      } else {
        const minItemHeight = Math.min(...itemsHeigth)
        const minItemIndex = itemsHeigth.indexOf(minItemHeight)
        item.style.top = minItemHeight + this.gap * 2 + 'px'
        item.style.left =
          (this.itemWidth + this.gap * 2) * minItemIndex + 'px'
        itemsHeigth[minItemIndex] =
          minItemHeight + this.gap * 2 + item.offsetHeight
      }
    })
  }
}

完成后,实例化 waterFall 实例,并传入配置对象即可:

window.onload = function () {
  new WaterFall({
    el: document.querySelector('.water-fall'),
    columns: 5,
    gap: 5,
  })
}

基本示例

Demo

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.