mdnice 主题
Opened this issue · 1 comments
/*自定义样式,实时生效*/
/* 全局属性
* 页边距 padding: 30px;
* 全文字体 font-family: ptima-Regular;
* 英文换行 word-break: break-all;
*/
#nice {
font-size: 15px;
letter-spacing: 0.05em;
color:#143250
}
/* 段落,下方未标注标签参数均同此处
* 上边距 margin-top: 5px;
* 下边距 margin-bottom: 5px;
* 行高 line-height: 26px;
* 词间距 word-spacing: 3px;
* 字间距 letter-spacing: 3px;
* 对齐 text-align: left;
* 颜色 color: #3e3e3e;
* 字体大小 font-size: 16px;
* 首行缩进 text-indent: 2em;
*/
#nice p {
margin: 1em 4px;
}
/* 一级标题 */
#nice h1 {
margin: 1.2em 0 1em;
padding: 0;
font-weight: 900;
color:#0277bd;
font-size: 25px;
}
/* 一级标题内容 */
#nice h1 span {
}
/* 一级标题修饰 请参考有实例的主题 */
#nice h1:after {
}
/* 二级标题 */
#nice h2 {
min-height: 32px;
line-height: 28px;
border-bottom: solid 1px #000000;
color: #0277bd;
display: inline-block;
border-bottom-width: 1px;
border-bottom-style: solid;
border-color: #0277bd;
padding-top: 5px;
padding-right: 0.5em;
padding-left: 0.5em;
margin-bottom: -3px;
font-size: 15px;
margin:1em auto;
padding: 0.5em 0 0.2em 0;
text-align: center;
width: 85%;
font-weight: bold;
display: flex;
flex-direction: column;
justify-content: center;
}
/* 二级标题内容 */
#nice h2 span {
}
/* 二级标题修饰 请参考有实例的主题 */
#nice h2:after {
}
/* 三级标题 */
#nice h3 {
margin: 1.2em 0 1em;
padding: 0;
font-weight: bold;
color:#0277bd;
}
/* 三级标题内容 */
#nice h3 span {
}
/* 三级标题修饰 请参考有实例的主题 */
#nice h3:after {
}
/* 无序列表整体样式
* list-style-type: square|circle|disc;
*/
#nice ul {
}
/* 无序列表整体样式
* list-style-type: upper-roman|lower-greek|lower-alpha;
*/
#nice ol {
}
/* 列表内容,不要设置li
*/
#nice li section {
margin: 10px 0;
}
/* 引用
* 左边缘颜色 border-left-color: black;
* 背景色 background: gray;
*/
#nice blockquote {
margin: 10px 0;
border: 0;
color: #616161;
quotes: none;
background:#e1f5fe
}
/* 引用文字 */
#nice blockquote p {
}
/* 链接
* border-bottom: 1px solid #009688;
*/
#nice a {
color: #0277bd;
border-bottom: 1px solid #0277bd;
}
/* 加粗 */
#nice strong {
color: #0277bd;
}
/* 斜体 */
#nice em {
}
/* 加粗斜体 */
#nice strong em {
}
/* 删除线 */
#nice del {
}
/* 分隔线
* 粗细、样式和颜色
* border-top: 1px solid #3e3e3e;
*/
#nice hr {
border: 1px solid #0277bd;
margin: 1.5em auto;
}
/* 图片
* 宽度 width: 80%;
* 居中 margin: 0 auto;
* 居左 margin: 0 0;
*/
#nice img {
}
/* 行内代码 */
#nice p code, li code {
color: #333333;
}
/* 非微信代码块
* 代码块不换行 display: -webkit-box !important;
* 代码块换行 display: block;
*/
#nice pre code {
}
#nice pre{
box-shadow: 0 0 10px #bbbbbb;
border-radius: 10px;
}
/*
* 表格内的单元格
* 字体大小 font-size: 16px;
* 边框 border: 1px solid #ccc;
* 内边距 padding: 5px 10px;
*/
#nice table tr th,
#nice table tr td {
}
/* 脚注文字 */
#nice .footnote-word {
color: #773098;
}
/* 脚注上标 */
#nice .footnote-ref {
color: #773098;
}
/* "参考资料"四个字
* 内容 content: "参考资料";
*/
#nice .footnotes-sep:before {
}
/* 参考资料编号 */
#nice .footnote-num {
}
/* 参考资料文字 */
#nice .footnote-item p {
}
/* 参考资料解释 */
#nice .footnote-item p em {
}
/* 行间公式
* 最大宽度 max-width: 300% !important;
*/
#nice .block-equation svg {
}
/* 行内公式
*/
#nice .inline-equation svg {
}
对象创建
本文原载自 http://js-professional.lxfriday.xyz/blog/2019/12/31/%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA,作为学习笔记总结呈现。
创建对象的几种基本方式
{}
对象字面量Object()
或者new Object()
new Constructor()
Object.create()
Object.assign()
工厂模式
function createPerson(name, age, job) {
const o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function() {
console.log(this.name);
};
return o;
}
const person1 = createPerson("Nicholas", 29, "Software Engineer");
const person2 = createPerson("Greg", 27, "Doctor");
每一次调用上面的 createPerson
工厂函数都可以创建一个对象,这个对象有 name
age
job
三个属性和一个 sayName
方法,依据传入的参数的不同,返回对象的值也会不同。
缺点:没有解决这个对象是一个什么类型的对象(没有更精确的对象标识,即没有精确的构造函数)。
构造函数模式
将工厂改造成构造函数之后,如下
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
console.log(this.name);
};
}
const person1 = new Person("Nicholas", 29, "Software Engineer");
const person2 = new Person("Greg", 27, "Doctor");
person1.sayName(); // Nicholas
person2.sayName(); // Greg
构造函数和工厂的区别:
- 没有显式创建对象;
- 直接把属性和方法赋值给
this
- 没有
return
使用构造函数创建对象将会有以下几个步骤:
- 在内存中创建一个新对象
- 新对象内部的
[[Prototype]]
指针指向构造函数的prototype
属性指向的对象; - 将构造函数的上下文
this
指向新创建的对象; - 执行构造函数内部的代码(给新对象添加属性);
- 如果构造函数
return
非null
的对象,那返回的就是这个对象,否则返回新创建的这个对象。没有return
时,隐式返回新创建的对象,return null
会返回新创建的对象;
缺点:每次实例化一个新对象,都会在内部创建一个 sayName
对应的匿名函数,而这个函数对所有实例来讲是没有必要每次都创建的,他们只需要指向同一个函数即可。
所以上面的代码经过改造之后,变成下面这样:
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName() {
console.log(this.name);
}
const person1 = new Person("Nicholas", 29, "Software Engineer");
const person2 = new Person("Greg", 27, "Doctor");
person1.sayName(); // Nicholas
person2.sayName(); // Greg
上述的做法虽然解决了重复创建匿名函数的问题,但是又引入了新的问题。
外面的 sayName
函数仅仅在构造函数中用到,如果对象需要很多个这样的函数,那么就需要在外部定义很多个这种函数,这无疑会导致代码很难组织。
原型模式
函数创建之后都会有一个 prototype
属性,每个使用该构造函数创建的对象都有一个 [[prototype]]
内部属性指向它。
使用原型的好处在于它所有的属性和方法会在实例间共享,并且这个共享的属性和方法是直接在原型上设置的。
function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
console.log(this.name);
};
const person1 = new Person();
person1.sayName(); // "Nicholas"
const person2 = new Person();
person2.sayName(); // "Nicholas"
console.log(person1.sayName == person2.sayName); // true
关于原型的工作原理,可以查看下面三篇文章,看完之后相信你对原型的认识比大多数人都要深刻!
- 前端面试必备 | 5000 字长文解释千万不能错过的原型操作方法及其模拟实现(原型篇:下)
- 前端面试必备 | 古怪的原型(鸡生蛋还是蛋生鸡)(原型篇:中)
- 前端面试必备 | 使用原型和构造函数创建对象(原型篇:上)
理解原型的层级
对象中属性的查找机制:
当从对象中访问一个属性的时候,JS 引擎将会按属性名进行查找。JS 引擎会先查找对象自身。如果找到了这个属性,就会停止查找并返回属性对应的值,如果在对象自身没有找到,则会通过原型链到原型对象中继续查找这个属性,如果找到了这个属性,就会停止查找并返回属性对应的值,否则会继续到上层原型链中查找,直到碰到 null
。
当一个属性添加到实例中时,这个属性会覆盖原型上的同名属性,这个覆盖指的是查找的时候不会到原型中查找同名属性。即使属性的值被赋值为 null
或 undefined
,它依然会阻止到原型链上访问。所以如果想要访问,就需要删除这个属性,使用 delete obj.xx
。
可以使用 hasOwnProperty
判断实例是否拥有某个属性,返回 true
则表示实例本身拥有该属性,否则表示它没有这个属性。当一个属性存在于原型链上时,可以访问到这个属性,但是使用 hasOwnProperty
将返回 false
。
in
操作符
in
操作符用在两个地方,一个是用在 for ... in
循环中,另一个是单独使用。单独使用时,返回 true
表示属性可以在对象或者其原型链上找到。
function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
console.log(this.name);
};
const person1 = new Person();
const person2 = new Person();
console.log(person1.hasOwnProperty("name")); // false
console.log("name" in person1); // true
person1.name = "Greg";
console.log(person1.name); // "Greg" - from instance
console.log(person1.hasOwnProperty("name")); // true
console.log("name" in person1); // true
console.log(person2.name); // "Nicholas" - from prototype
console.log(person2.hasOwnProperty("name")); // false
console.log("name" in person2); // true
delete person1.name;
console.log(person1.name); // "Nicholas" - from the prototype
console.log(person1.hasOwnProperty("name")); // false
console.log("name" in person1); // true
可以通过组合使用 hasOwnProperty
和 in
来实现判断一个属性是否存在于原型链上:
function hasPrototypeProperty(object, name) {
return !object.hasOwnProperty(name) && name in object;
}
const obj = Object.create({ name: "lxfriday" });
console.log(obj);
console.log(hasPrototypeProperty(obj, "name"));
关于对象属性的枚举顺序
for ... in
Object.keys()
Object.getOwnPropertyNames/Symbols()
和 Object.assign()
在处理属性枚举顺序的时候会有很大差别。
for ... in
Object.keys()
没有确定的枚举顺序,它们的顺序取决于浏览器实现。
而 Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
和 Object.assign()
是有确定的枚举顺序的。
- 数字键会按照升序先枚举出来;
- 字符串和 symbol 键按照插入的顺序枚举出来;
- 对象字面量中定义的键会按照代码中的逗号分割顺序枚举出来;
const k2 = Symbol("k2");
const k1 = Symbol("k1");
const o = { 1: 1, [k2]: "sym2", second: "second", 0: 0, first: "first" };
o[k1] = "sym1";
o[3] = 3;
o.third = "third";
o[2] = 2;
// [ '0', '1', '2', '3', 'second', 'first', 'third' ]
console.log(Object.getOwnPropertyNames(o));
// [ Symbol(k2), Symbol(k1) ]
console.log(Object.getOwnPropertySymbols(o));
对象的迭代
ES 2017 引入了两个静态方法来将对象的内容转换为可迭代的格式。
Object.values()
返回对象值构成的数组; Object.entries()
返回一个二维数组,数组中的每个小数组由对象的属性和值构成,类似于 [[key, value], ...]
。
const o = { foo: "bar", baz: 1, qux: {} };
console.log(Object.values(o)); // ["bar", 1, {}]
console.log(Object.entries(o)); // [["foo", "bar"], ["baz", 1], ["qux", {}]]
在输出的数组中,非字符串的属性会转换成字符串,上述的两个方法对引用类型是采取的浅拷贝。
const o = { qux: {} };
console.log(Object.values(o)[0] === o.qux); // true
console.log(Object.entries(o)[0][1] === o.qux); // true
symbol 键名会被忽略掉。
const sym = Symbol();
const o = { [sym]: "foo" };
console.log(Object.values(o)); // []
console.log(Object.entries(o)); // []
原型的另一种写法
上面的例子中,给原型赋值都是一个个赋,比较繁琐,看看下面的赋值方式:
function Person() {}
Person.prototype = {
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName() {
console.log(this.name);
}
};
上面的例子中,Person
的原型直接指向一个对象字面量,这种方式最终的结果和前面的单个赋值是一样的,除了原型的 constructor
属性,constructor
不再指向 Person
构造函数。默认情况下,当一个函数创建的时候,会创建一个 prototype
对象,并且这个对象上的 constructor
属性也会自动指向这个函数。所以这种做法覆盖了默认的 prototype
对象,意味着 constructor
属性指向新对象的对应属性。虽然 instanceof
操作符依然会正常工作,但是已经无法用 constructor
来判断实例的类型。看下面的例子:
const friend = new Person();
console.log(friend instanceof Object); // true
console.log(friend instanceof Person); // true
console.log(friend.constructor == Person); // false
console.log(friend.constructor == Object); // true
如果 constructor
属性很重要,那么你可以手动的给它修复这个问题:
function Person() {}
Person.prototype = {
constructor: Person,
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName() {
console.log(this.name);
}
};
不过上面的设置方法有一个问题,constructor
的属性描述如下
{
value: [Function: Person],
writable: true,
enumerable: true,
configurable: true
}
我们再看看 Object.prototype.constructor
:
{
value: [Function: Object],
writable: true,
enumerable: false,
configurable: true
}
我们自己赋值时枚举属性会被默认设置为 true
,所以需要通过 Object.defineProperty
来设置不可枚举:
Object.defineProperty(Person.prototype, "constructor", {
value: Person,
enumerable: false,
configurable: true,
writable: true
});
原型存在的问题
我们知道原型属性对所有实例是共享的,当原型属性是原始值时没有问题,当原型属性是引用类型时将会出现问题。看看下面的例子:
function Person() {}
Person.prototype = {
constructor: Person,
name: "Nicholas",
age: 29,
job: "Software Engineer",
friends: ["Shelby", "Court"],
sayName() {
console.log(this.name);
}
};
const person1 = new Person();
const person2 = new Person();
person1.friends.push("Van");
console.log(person1.friends); // "Shelby,Court,Van"
console.log(person2.friends); // "Shelby,Court,Van"
console.log(person1.friends === person2.friends); // true
上述例子中,原型属性 friends
原本是一个包含两个字符串的数组,但是由于 person1
修改了它的内容,导致了原型上的这个属性被更改了,所以 person2
访问的时候也会打印三个字符串。
由于这个问题,原型模式并不会单独使用,我们经常会结合构造函数和原型来创建对象。
最后
往期精彩:
- 前端面试必备 | 5000字长文解释千万不能错过的原型操作方法及其模拟实现(原型篇:下)
- 前端面试必备 | 古怪的原型(鸡生蛋还是蛋生鸡)(原型篇:中)
- 前端面试必备 | 使用原型和构造函数创建对象(原型篇:上)
- 前端面试必会 | 一文读懂 JavaScript 中的 this 关键字
- 前端面试必会 | 一文读懂现代 JavaScript 中的变量提升 - let、const 和 var
- 前端面试必会 | 一文读懂 JavaScript 中的闭包
- 前端面试必会 | 一文读懂 JavaScript 中的作用域和作用域链
- 前端面试必会 | 一文读懂 JavaScript 中的执行上下文
- IntersectionObserver 和懒加载
- 初探浏览器渲染原理
- CSS 盒模型、布局和包含块
- 详细解读 CSS 选择器优先级
关注公众号可以看更多哦。
感谢阅读,欢迎关注我的公众号 云影 sky,带你解读前端技术,掌握最本质的技能。关注公众号可以拉你进讨论群,有任何问题都会回复。