blog's Introduction
blog's People
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
。所以 person
和 obj
引用是同一个变量,函数内部的修改,会反映到函数外来。
这里要注意区分访问变量的方式和传递参数的方式不是一回事。
person
是按值传递给obj
的,但是person
和obj
都是按引用来访问同一个对象的,因为person
中的值就是对象的引用。
Flex布局
Flex 布局是什么?
Flex 是 Flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性。任何一个容器都可以指定为 Flex 布局,行内元素也可以使用 Flex 布局。
注意,设为 Flex 布局以后,子元素的
float
、clear
和vertical-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
:主轴为垂直方向,起点在下沿。
flex-wrap
flex-wrap
属性定义,如果一条轴线排不下,如何换行。
nowrap
(默认):不换行。wrap
:换行,第一行在上方。wrap-reverse
:换行,第一行在下方。
flex-flow
flex-flow
属性是 flex-direction
属性和 flex-wrap
属性的简写形式,默认值为 row nowrap
。
justify-content
justify-content
属性定义了项目在主轴上的对齐方式。
flex-start
(默认值):左对齐(假设主轴为从左到右,下同)flex-end
:右对齐center
: 居中space-between
:两端对齐,项目之间的间隔都相等。space-around
:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍。
align-items
align-items
属性定义项目在交叉轴上如何对齐。
flex-start
:交叉轴的起点对齐。(假设交叉轴从上到下,下同)flex-end
:交叉轴的终点对齐。center
:交叉轴的中点对齐。baseline
: 项目的第一行文字的基线对齐。stretch
(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。
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-shrink
和 flex-basis
的简写,默认值为 0 1 auto
。后两个属性可选。
有两个快捷值:
auto
(1 1 auto
) 和none
(0 0 auto
)
aligin-item
aligin-item
属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。
从JavaScript的角度理解Java的lambda表达式和方法引用
最近学习Java第一次看到lambda表达式和方法引用时有些懵逼,OOP的Java突然混入函数式着实让我不好理解,不过我发现如果从JavaScript的角度去理解这两个特性就丝滑的多了。
首先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
配置 useBuiltIns
和 corejs
属性,我们实现了 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)
obj
,propertyName
:要处理的对象和属性。
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 在后台完成了一系列的动作:
- 创建
String
类型的一个实例; - 在实例上调用指定的方法;
- 销毁这个实例。
可以想象为以下三行代码:
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,花瓣网等。
瀑布流布局效果(花瓣网)
瀑布流布局实现
基本布局规则
要想实现瀑布流布局,需要对瀑布流布局的基本规则有所了解。
首先瀑布流中所有元素的宽度为定值,即容器宽度除以列数(为简化说明暂时省略列间隙),高度根据元素的比例自适应。水平排列第一行元素后,效果如图所示:
这里要注意图上的红色线条,它表示了当前布局垂直方向上最短的一项,当有下一项插入时,应将其放入垂直方向最短一项的下方,效果如图所示:
此时垂直方向上的最短项发生了改变,下一张图片应放入更新后的最短项下放,效果如图所示:
之后按照该规则依次插入元素,即可实现瀑布流布局。
具体代码实现思路
首先完成 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'
})
}
}
对每一个元素的位置进行计算,并通过 top
和 left
值进行设置:
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,
})
}
基本示例
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.