仿天猫首页的总结

从最初的静态页面仿写开始,到后面添加注册登录功能,陆陆续续的在笔记里面写了很多东西,今天把它放到网上去了,于是想把这些笔记整理一下顺便在总结复习一下。加深印象,毕竟通过它也学到不少东西。

CSS部分

WebFont制作icon

好的吧,在写这个之前,做小图标都是用精灵图片,好麻烦的,当我发现另存为下来的图片文件夹下没有这些小图标的时候,准备F12把图标弄下来,却发现这是什么鬼,乱码啊不是图片啊也不是背景啊。于是就去Google,然后就发现原来这个是WebFont,之前知道有WebFont这个东西,但是没怎么用过,原来可以用图标字体来实现icon,而且网上有很多资源,既然是仿写天猫那就选阿里巴巴矢量库啊,用了之后发现真的好方便啊。

z-index的秘密

这个页面的头部和Banner部分的层级关系比较多,开始写的时候写着写着发现层级关系不对,而且我加大z-index,减小z-index的值之后它的表现都让我挺失望的,摸不着头脑的感觉,深入了解了之后才知道原来是我不懂它,它并不是一个简单的css属性,人家是有背景有靠山的。为此我还专门写了一篇文章以表纪念我想更了解z-index

晕乎乎的框模型与视觉格式化模型

在了解z-index的时候发现它在规范里面所属的章节是Visual formatting model,也就是视觉格式化模型,好专业的名词,我感觉我好像不知道这么个东西,然后我带着我的好奇心从头看了一遍,我不得不承认我头快晕了。但是作为一个轻微强迫症患者,既然看到了而且自己不清楚那就得学习学习,看的蛮吃力的,虽然还不是很透彻但是好在有个大概的理解了,以后还要多温习理解。这样费脑细胞的知识当然需要纪念框模型与视觉格式化模型

BFC的神秘面纱

视觉格式化模型里面的内容真的好多,随便拿一个出来都可以有要学习要注意的,BFC可以解释很多习以为常的现象,还可以解释一些我疑虑的地方,BFC其实就是块级框的布局规则,相当于CSS页面布局的地基,并非什么神奇的概念。相应的IFC是行级框的布局规则。BFC可以有一些很有用的应用BFC的应用

隐藏元素的各种方法

想要一个元素不在页面上显示出来,之前知道的有display:nonevisibility: hiddentext-indent:-9999emopacity:0,height:0;overflow:hidden其实还有很多其他的方法,而且这些方法有些差别,如下:

  • display:none 不占据空间,无法点击,内容可能被搜索引擎忽略,会引起reflow和repaint,设置后子元素没办法显示出来
  • visibility: hidden占据空间,无法点击,不会引起reflow和repaint,设置后子元素设置visiblility:visible可以单独显示出来
  • text-indent:-9999em占据空间,点击不到
  • opacity:0占据空间,可以点击
  • position:absolute;opacity:0不占据空间,可以点击
  • height:0;overflow:hidden不占据空间,无法点击
  • position:absolute;clip:rect(1px 1px 1px 1px);不占据空间,无法点击(矩形裁剪只能作用于绝对/固定定位的元素上)
  • position:absolute;top:-999em不占据空间,无法点击
  • position:relative;top:-999em占据空间,无法点击

高度自适应

height:100%可以实现元素高度跟随这个元素的父元素容器的高度,但是前提是父元素有除auto外的高度值。有时候我们却正好需要让这个父元素的高度根据内容自适应,这个时候设置height:100%之类的百分数值可就没有用咯。要解决这个问题的方法是使用下面的绝对定位方法实现高度自适应

1
2
3
4
5
6
7
8
9
.parent{
position: relative;
}
.child{
width: 200px;
position: absolute;
top:0;
bottom: 0;
}

厉害的writing-mode属性

以前没有用过这个属性,有一次看张鑫旭前辈的博客的时候,就看到了一篇关于它的文章,进去学习了一番,原来这么厉害呢。看完了之后也没有什么实际应用过,这次仿写天猫的时候,那个侧边栏提示菜单的那里的购物车三个字是竖着的,本来打算用<br>换行实现,但是觉得语义化不太好啊,突然灵机一想就想起了这个属性,yeah~完美实现。
这个writing-mode将页面默认的水平流改成了垂直流,那些基于原本水平方向才适用的规则全部都可以在垂直方向适用!比如说:水平方向也能margin重叠、margin:auto能实现垂直居中、margin/padding的百分比值可以基于高度等。改变CSS世界纵横规则的writing-mode属性

JavaScript部分

面向对象

在写该页面的js的时候,发现这个页面中有两个功能一样的搜索框,我就想既然这样是不是可以用用面向对象的方式,顺便还能结合实际例子理解相关的知识。因为这两个搜索框的功能是一样的,所以也就没有用到继承,只是创建了两个搜索框实例。哈哈,后来增加注册登录功能的时候,发现注册页面也有一个一样的搜索框。

JavaScript的面向对象编程和大多数其他语言如Java、C++的面向对象编程都不太一样,大多数语言是基于类的(class-based)面向对象,对象依靠类来产生,而JavaScript没有类的概念(虽然ES6有了calss,但是它只是一个语法糖),JavaScript是基于原型的 (prototype-based) 面向对象,对象则是依靠构造函数利用 原型(prototype)构造出来的。
ECMA262把对象定义为:

无序属性的集合,其属性可以包含基本值,对象或者函数

也就是说对象是一组没有特定顺序的名值对,其中值可以是数据或者函数。每个对象都是基于一个引用类型创建的,可以是原生类型也可以是自定义类型。创建自定义对象的最简单的方式就是创建一个Object的实例,这是基于原生类型创建。而创建自定义类型最常见的方式为组合使用构造函数模式和原型模式。

构造函数模式

构造函数可以创建特定类型的对象,Object(),Array()是原生的构造函数,可以直接使用来创建相应类型的对象。而通过自定义构造函数,从而定义自定义对象类型的属性和方法可以创建自定义类型的对象

1
2
3
4
5
6
7
8
9
10
11
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
}
}
var person1 = new Person("cc", 20, "student");
var person2 = new Person("gg", 28, "teacher");

如上例所示,构造函数直接给this对象加属性及方法,不需要显示创建对象,也不需要返回值。通过new调用该构造函数即可创建该自定义类型的对象.通过new方式调用构造函数会经历以下4个步骤
1.创建一个新对象
2.将构造函数的作用域赋给新对象,此时this指向这个新对象
3.执行构造函数中的代码
4.返回新对象
由于JavaScript中的函数也是对象,所以每次定义一个函数就相当于实例化了一个对象,那么当构造函数中定义了方法时(如上例中的sayName方法),该方法将会在每个实例上重新创建一遍。这使得不同实例中的同名函数不相等。

原型模式

理解prototype

1
2
3
4
5
6
7
8
9
10
11
12
function Person(){}
Person.prototype.name = "cc";
Person.prototype.age = "20";
Person.prototype.job = "student";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.hobby = "music"

创建的每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向函数的原型对象,而这个原型对象的作用是:包含那些 可以 由特定类型的所有实例共享的属性和方法。注意是可以共享的属性和方法,也就是说不会包含实例特有的属性(如person1.hobby)。此时,person1和person2中的sayName方法是相等的,因为他们共享同一个sayName方法。
默认情况下,所有原型对象都会自动获得一个constructor属性,该属性包含一个指向 prototype属性所在函数(Person())的指针
调用构造函数创建一个新实例(person1/person2)后,该实例内部将包含一个指针(内部指针)[[Prototype]],指向构造函数的原型对象。
总结上面几句话:Person.prototype指向原型对象,原型对象中的Person.prototype.constructor指回Person,person1与person2的内部属性[[Prototype]]指向原型对象

person1/person2中都不包含属性和方法,但是却可以调用person1.name,person2.sayName()等。这是通过查找对象属性的过程来实现的,过程如下:

  • 每当代码读取到某个对象的某个属性/方法时,都会执行一次搜索,目标是具有给定名字的属性/方法
  • 搜索从对象实例本身开始,若在实例中找到该属性就返回该属性;若没有找到,就继续搜索指针[[Prototype]]指向的原型对象,在原型对象中找到,则返回该属性/方法

当我们添加下列代码

1
person1.name = "cshine";

虽然可以通过对象实例访问保存在原型中的值,但是不能通过 对象实例 重写原型中的值,当对象实例添加了一个与 原型对象中的属性同名的属性(person1.name),此时访问person1.name得到的是”cshine”而不是”cc”,因为对象实例的属性会屏蔽原型对象中的那个属性,但是不会修改它,也就是说原型中的name属性的值仍然为”cc”。
使用delete操作符可以删除 实例属性,恢复对原型中属性的访问

简单点的原型语法

1
2
3
4
5
6
7
8
9
10
11
12
function Person(){}
Person.prototype = {
constructor:Person,
name:"cc",
age:20,
job:student
sayName:function(){
alert(this.name);
}
}
var person = new Person();
person.sayName();

我们可以用一个一个包含所有属性和方法的 对象字面量 来重写整个原型对象,这样可以避免每次添加都要敲 Person.prototype

  • 注意1:此时 constructor属性将不再 指向Person函数,而是指向 Object函数。需要在 对象字面量中 通过constructor:Person将其设为适当的值
  • 注意2:如果先创建了Person实例,然重写后再重写其原型对象,此时调用属性或者方法会报错,也就是如果像下面这样会报错
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function Person(){}
    //先创建
    var person = new Person();
    //再重写其原型对象
    Person.prototype = {
    constructor:Person,
    name:"cc",
    age:20,
    job:student
    sayName:function(){
    alert(this.name);
    }
    }
    person.sayName();//报错

这是因为调用构造函数创建对象实例person时,实例person的[[Prototype]]指向的是最初原型对象,而之后再重写了原型对象,添加的属性和方法是属于这个新的原型对象,而不属于最初的那个原型对象,所以person.sayName();会报错,因为person指向的那个原型对象中根本没有sayName方法

模式的常用选择

原型模式存在以下问题:

  • 所有实例在默认情况下取得相同的值
  • 基本值共享可能会有些不方便,但可以通过实例属性屏蔽原型属性
  • 引用类型属性共享将有问题,只要一个实例修改,其他实例都会修改

而构造函数模式中定义的属性刚好不是共享的,是每个实例都不同的,所以创建自定义类型最常用的方法就是组合使用构造函数模式和原型模式

  • 构造函数模式用于定义 实例属性(也就是不想共享的属性)
  • 原型模式用于定义方法和共享的属性

原型链

JavaScript实现继承主要是通过原型链来实现的。基本思想是利用原型链让一个引用类型继承另一个引用类型的属性和方法。
原型链:B的原型对象 等于 另一个类型A的实例,此时B的原型对象将包含一个[[Prototype]]指针,该指针指向 A的原型对象,A的原型对象中包含着一个指向A的构造函数的指针constructor,假如A的原型对象又是另一个类型C的实例……..,如此递进就形成原型链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function A(){
this.aproperty = true;
}
A.prototype.getAValue = function(){
return this.aproperty;
}
function B(){
this.bproperty = false;
}
B.prototype = new A();
B.prototype.getBValue = function(){
return this.bproperty;
}(
var ins = new B();
alert(ins.getAValue()); //输出true

上例中,B类型继承了A类型,是通过将B的原型对象作为A的实例来实现的,本质是重写原型对象。所以原来存在于A的实例中的所有属性和方法,现在也都存在于B的原型对象中,所以B得实例也就可以访问到A中的方法。
但是此处继承来的属性和方法也有区别,因为原型对象中只会包含通过原型对象添加的属性,在构造函数中添加的实例属性不会出现在原型对象的属性里,而是会出现在实例对象中通过

  • B继承得来的aproperty属性时位于B的原型对象B.prototype中的,因为aproperty属性是通过构造函数定义的实例属性,不是共享的。B的原型对象是A的实例,当然就存在与实例中了。
  • B继承得来的getAValue()方法仍然是存在于A的原型对象A.prototype中,因为它是共享的原型方法。

所有引用类型默认都继承了Object,这个继承也是通过原型链来实现的, 所以所有函数的默认原型都是Object的实例,默认原型都会包含一个内部指针[[Prototype]]指向Object.prototype
注意:通过原型链实现继承时,不能用对象字面量创建原型方法,否则这会重写原型链
原型链的问题:

  • 包含引用类型值的原型属性会被所有实例共享,原型链中上一个类型(A)的实例属性会变为 继承它的那个子类型(B)的原型属性,如果是引用类型值,就被共享了
  • 在创建子类型B的实例时,不能向超类型A的构造函数中传递参数

借用构造函数(伪造对象/经典继承)

为解决原型链方式中 引用类型值带来的问题而使用的一种技术
基本思想: 在子类型构造函数的内部调用超类型的构造函数,可以通过apply() 或者 call()方法在新创建的对象上执行构造函数
可以在子类型构造函数中向超类型构造函数传递参数
问题是无法进行函数复用

继承的常用方式

常用的方式是组合继承,也叫伪经典继承,是将原型链和借用构造函数技术组合起来的继承模式
思路:用原型链实现对原型属性和方法的继承,借用构造函数来实现对实例属性的继承

以上内容大多数为书上的概念,觉得自己理解的还不够,用自己的话可能不严谨有错误,以后还要多多学习,感觉还是需要再实际中应用才能更深的理解,所以边攒经验边理解吧。不能奢求一劳永逸。

闭包

闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的常见方式就是在函数内部创建另一个函数
内部函数可以访问包含函数中的变量,因为内部函数的的作用域链中包含了 包含函数的作用域。
感觉上面的面向对象部分写的好多啊,这里就写下写这个页面遇到的相关点,其他的感觉可以单独写一篇了。
有一个常见的错误会出现在循环中创建闭包这个情况下,因为闭包只能取得包含函数中任何变量的最后一个值。

1
2
3
4
5
6
7
8
9
function fn(){
var a = [];
for(var i=0;i<10;i++){
a[i] = function(){
alert(i);
}
}
return a;
}

在上面这个例子中,在返回的函数数组a中,似乎每个函数应该弹出相应的i值,但事实是他们弹出的都是相同的一个值 —— 10。
因为这个函数数组中的每个函数都保存着fn()函数的活动对象,而fn()函数的活动对象里只有一个i值,所以这些函数引用的是同一个i值,而循环的时候i值一直在变化,最后变为10,所以每个函数弹出的都是同一个为10的i值
那怎么解决这个问题,可以采用加一个匿名函数并立即执行该匿名函数的方式,其中i作为该匿名函数的参数传入

1
2
3
4
5
6
7
8
9
10
11
function fn(){
var a = [];
for(var i=0;i<10;i++){
a[i] = (function(num){
return function(){
alert(num);
}
})(i);
}
return a;
}

此时,将新建的匿名函数执行的返回值赋给a[i],也就是将最里面的那个匿名函数赋给a[i],功能和上面的例子是一样,不过此时i作为参数传入,i的值每次会复制给num,而每次的num显然不是同一个,所以就可以实现每次弹出相应值了。

PHP与MySql部分

本来只是仿写了天猫首页,之后过了一段时间,想着不然给它加上注册登录的功能吧,顺便可以借此机会了解了解后台的知识,然后就花了几天时间把PHP和MySql基础教程这本书过了一遍,学习完之后对前后台的交互有所了解,PHP基本的上手还是蛮简单的,但是这个程度真的就只是了解,以后还是要深入学习学习。
前面在本地写的时候,都挺顺利的,但是上传到服务器后,登录功能一直报错:500 服务器内部错误。好吧,上网查了一些资料,也照着做了,可是就是没有用啊没有用。最后在一篇十分不起眼的文章里发现在PHP的配置文件里面,我没有打开display_error,所以在浏览器里面是看不到详细错误的,那一刻我真的太兴奋了,赶紧找到文件打开它,在浏览器一试果然出来了,原来是因为PHP版本的问题,在低版本的PHP中是不能将一个函数的返回值作为另一个函数的参数来使用的,找到原因后,改了PHP重新上传,果然OK啦~开心开心开心啊。

最后的几句话

啊啊啊,越总结越发现自己会的越少,前端的技术更新的好快,不过还是希望自己能踏实的先把这些地基打好,毕竟万变不离其宗!地基打好以后盖楼才会快。