Cauil's Blog

陌上花开,可缓缓归矣


  • 首页

  • 归档

  • 标签

深入理解Javascript: 比较

发表于 2015-10-19 | 分类于 js

开始这个之前我们先看几个例子

var a = [1, 2]
var b = [1, 2]
a == [1, 2] // false
a == b // false

var c = {a: 1, b: 2}
var d = {a: 1, b: 2}
c == d // false

var str = 'test'
str == 'test' // true

str.toUpperCase() // 'TEST'
str // 'test'

为什么有这种现象发生呢?

1.JS的原始值与对象

原来JS分为两种对象,一个是原始类型,有undefined、null、布尔值、数字和字符串;一个是对象类型(数组、函数等);

原始类型值是不可更改的,任何方法都无法更改一个原始值,如上面例子中str经过转换大写之后还是‘test’;原始类型值的比较是值的比较;

对象类型值的比较并非值的比较,即使两个对象包含同样的属性及相同的值,如上例子的a与b、c与d的比较都为false;

对象的比较为引用的比较,当且仅当它们引用同一个基对象时,它们才相等,如下:

var a = [1, 2]
var b = a
a == b // true

2.== 与 ===

再来看一个例子:

1 == '1' // true
1 === '1' // false
var undefined;
undefined == null; // true
1 == true;   // true
2 == true;   // false
0 == false;  // true
0 == '';     // true
NaN == NaN;  // false
[] == false; // true
[] == ![];   // true

为啥会有前面两行的结果呢?

原来‘==’运算符用来检测两个操作数是否相等,这里相等的定义非常宽松,可以允许进行类型转换;

而‘===’运算符用来检测两个操作数是否严格相等,会比较两个操作数的类型是否相同;

===运算规则

‘===’运算符首先计算其操作数的值,然后比较这两个值,比较过程没有任何类型转换;

  • 如果两个值类型不同,返回false;

  • 如果两个值都为null或者undefined,返回true;

  • 如果两个值都为布尔值true或者false,返回true;

  • 如果其中一个为NaN,则返回false,即使另一个也为NaN;

  • 如果两个值为数字且数值相等,则返回true,0与-0也返回true;

  • 如果两个字符串的长度或者内容不同,则返回false;

  • 如果两个引用值指向同一个对象,则返回true,其他对象比较都返回false;

==运算规则

‘==’运算符跟‘===’类似,不过比较并不严格。如果两个操作数类型相同,则和上文所述的严格相等的比较规则一样;

如果两个操作数不是同一类型,那么相等运算符会尝试进行一些类型转换,然后进行比较:

  • 如果一个值为null,另一个为undefined,则它们相等;

  • 如果一个值是数字,另一个是字符串,先将字符串转换为数字,然后使用转换后的值进行比较;

  • 如果其中一个是true,则将其转换为1再进行比较;如果是false,则转换为0再进行比较;

  • 如果一个值为对象,另一个值是数字或字符串,则使用下一小节要讲的类型转换规则转换为原始值,然后再进行比较;对象通过toString()方法或valueOf方法转换为原始值;JS语言核心的内置类首先尝试使用valueOf(),再尝试toString(),除了日期类,日期类只使用toString()转换。那些不是内置对象则通过各自实现中定义的方法转换为原始值;

  • 其他不同类型之间的比较均不相等;

如'1' == true返回true,其中true转换为1,‘1’转换为1,因为两个数字的值相等,所以返回true。

3.类型转换

类型转换有隐式和显式转换,下图为转换的规则:

http://7xj29n.com1.z0.glb.clouddn.com/类型转换.jpg

显示转换

做显示转换最简单的方法就是使用Boolean()、Number()、String()或Object()函数;

注意这些函数与它们作为函数构造器是有区别的,作为函数构造器是使用new Number(),这种得出来的是属于包装对象;

如下:

Number("3")
new Number("3")
typeof Number("3") // number
typeof new Number("3") // object

隐式转换

隐式转换是通过上下语境或者运算符来转换的;

例如‘+’运算符有一个操作数为字符串时,另一个操作数不为字符串时会隐式转换为字符串;’-‘运算符会隐式把两个操作数转换为数字;

再如一元运算符‘!’会把操作数转换为布尔值并取反;如:

12 + '1' // '121'
new Number("3") - [1] // 2
!![] // true

对应的转换规则如上面图中所示,下面只来说说对象转换为原始值的方式(这里只谈原生对象,而非宿主对象的转换,宿主对象根据各自的算法进行转换);

1.对象转换为字符串

  • 如果对象具有toString()方法,且调用这个方法返回一个原始值,则对象先转换为原始值,然后再通过上图的转换规则转换为字符串;
  • 否则JS会调用valueOf()方法,且调用这个方法返回一个原始值,则对象先转换为原始值,然后再通过上图的转换规则转换为字符串;
  • 如果对象无法从上面两个方法获得一个原始值,这时它将抛出一个类型错误异常;

例如计算[1, 2, 3] + 'abc'返回'1,2,3abc'

首先[1, 2, 3]调用toString()方法返回‘1,2,3’然后再执行字符串的+运算,返回'1,2,3abc'

2.对象转换为数字

这个与上面转换为字符串相反,先调用valueOf(),失败再尝试toString();

  • 否则JS会调用valueOf()方法,且调用这个方法返回一个原始值,则对象先转换为原始值,然后再通过上图的转换规则转换为字符串;
  • 如果对象具有toString()方法,且调用这个方法返回一个原始值,则对象先转换为原始值,然后再通过上图的转换规则转换为字符串;
  • 如果对象无法从上面两个方法获得一个原始值,这时它将抛出一个类型错误异常;

例如计算[1, 2, 3] - 1返回NaN

首先[1, 2, 3]调用valueOf()方法,而此方法返回一个对象而不是原始值,则调用toString()方法,返回‘1,2,3’然后再转换为数字,根据字符串转换为数字的规则,为NaN,NaN - 1返回NaN。

注意:日期对象有点特别,通过valueOf和toString方法返回的原始值将被直接使用;

4.后记

通过上面的一些例子我们知道JS原始类型与对象类型的一些转换规则以及JS的内部实现,有机会再聊聊JS的常见运算符。

CSS:overflow

发表于 2015-09-22 | 分类于 css

1.overflow基本属性

值:visible、hidden、scroll、auto、inherit

初始值:visible

应用于:块级元素和替换元素

继承值:无

计算值:根据指定确定

overflow-x和overflow-y

当容器高宽都不足以容下图片时,overflow-x:hidden会把x方向的隐藏掉、y方向出现滚动条;为什么会出现这种情况呢?

如果overflow-x和overflow-y值相同,则等同于overflow;

如果overflow-x和overflow-y值不同,且其中一个值为visible,另一个值为hidden、scroll、auto,则visible会被重置为auto;这就是为什么上面的y方向出现滚动条;

兼容性

不同浏览器滚动条的表现不一样;

宽度设定机制,见下面一个例子:

<div class="box"><div class="content"></div></div>

.box { width: 400px; height: 100px; overflow: auto; }
.content { width: 100%; height: 200px; background-color: #beceeb; }

在IE7中,垂直和水平方向都出现了滚动条;这是宽度设定机制差异导致的,IE7浏览器把100%宽度算成了外部容器的400px,而垂直方向出现滚动条后,会占用宽度的,从而外部容器水平方向没有400px,从而水平方向出现了滚动条。

如何避免这种情况呢,直接去掉width: 100%就好了。

IE8没有这种情况;

如何使overflow属性有效

  • 非display: inline水平

  • 对应方位的尺寸限制;width/height/max-width/max-height/absolute拉伸;

  • 对于单元格td等,还需要table为table-layout:fixed状态才行;

两个IE7的bug:

(1)IE7浏览器下,按钮里的文字越多,按钮两侧的padding留白就越大!

解决方案:给所有按钮添加css样式overflow:visible

(2)

.size{ max-width: 200px; max-height: 300px; overflow: auto; }
<div class='size'><img src="mm1.jpg" width="256" height="191"></div>

按理论只有水平出现滚动条,结果垂直方向也出现了;

2.overflow与滚动条

滚动条出现的条件

1.overflow:auto/overflow:scroll //<html>、<textarea>

2.当子元素的水平或者垂直方向超过容器对应方向的最大限度;

body/html与滚动条

无论什么浏览器,默认滚动条均来自<html>而不是<body>标签;

原因:新建一个空白HTML页面,<body>默认.5em margin值,如果滚动条来自<body>,则应该滚动条与浏览器边框有边距;而事实是滚动条是紧贴着浏览器边框的;

IE7-浏览器默认类似: html { overflow-y: scroll; }
IE8+等浏览器默认:html { overflow: hidden; }

所以,如果我们想要去除页面默认滚动条,只需要:

html { overflow: hidden; }

而没必要把也拉下水:

html, body { overflow: hidden; } //不需要body标签

JS与滚动高度

chrome浏览器是:document.body.scrollTop;

其他浏览器是:document.documentElement.scrollTop;

目前,两者不会同时存在,因此,有人会写成:

var st = document.body.scrollTop + document.documentElement.scrollTop

建议使用:

var st = document.body.scrollTop || document.documentElement.scrollTop

overflow的padding-bottom缺失现象

.box { width: 400px; height: 100px; padding: 100px 0; overflow: auto; }

在chrome浏览器下面的padding-bottom可以滚动出来;而其他浏览器都不存在;

可能导致的: 不一样的scrollHeight(元素内容高度)

滚动条的宽度机制

滚动条会占用容器的可用宽度和高度;

下面这种方法可以计算滚动条的宽度:

.box {width: 400px; overflow: scroll; }
.in {*zoom: 1; /* for IE7 */}

<div class="box">
    <div id="in" class="in"></div>
</div>

console.log(400 - document.getElementById("in").clientWidth);

测得的结果:IE7+/Chrome/FireFox(win7)-均是17像素;

overflow:auto的潜在布局隐患

滚动条会占用容器尺寸,原本和谐的布局,滚动条出现后可能挂掉;

水平居中跳动问题

.container { width: 1150px; margin: 0 auto; }

问题的修复:

1.html { overflow-y: scroll; } // 太丑

2..container { padding-left: calc(100vw - 100%);}

3.overflow与BFC

BFC(block formatting context) - ‘块级格式化上下面’

BFC有一个特点:页面之结界,内部元素再怎么翻云覆雨都不会影响外部;

overflow与BFC

overflow有三种属性会触发BFC:auto、scroll、hidden

有三种常用用途:

清楚浮动影响、避免margin穿透问题、两栏自适应布局

CSS:浮动

发表于 2015-08-21 | 分类于 css

什么是浮动,浮动有什么作用?

1.浮动的前世今生

从Netscape1以来,就可以通过声明让图像浮动,如:

<img src="b5.gif" align="right">

这会导致一个图像浮动到右边,而允许其他内容如文本“围绕”该图像。

可见浮动的初衷是为了实现文字环绕效果;

过去只可能浮动图像。但CSS允许浮动任何元素,从图像到段落再到列表,所有元素都可以浮动。在CSS中,这种行为是用属性float实现。

2.float取值

值:left、right、none、inherit

初始值:none

应用于:所有元素

继承值:无

计算值:根据指定确定

3.浮动的特性:包裹与破坏

什么是包裹(对本身而言):包裹是收缩、坚挺(有高度了)、隔绝(内部内容与外界不相关)

具有包裹的其他小伙伴:

  • display: inline-block/table-cell/…

  • position: absolute/fixed/sticky

  • overflow: hidden/scroll

什么是破坏(对父元素而言):容器被破坏,父元素的高度塌陷

具有破坏的其他小伙伴:

  • display: none

  • position: absolute/fixed/sticky

对于浮动元素有几点要记住。

首先,会以某种方式将浮动元素从文档的正常流中删除,不过它还是会影响布局。

采用CSS的特有方式,浮动元素几乎自成一派,不过它们还是对文档的其他元素有影响。

4.清除浮动

由于浮动具有破坏性,那我们怎么清楚浮动带来的影响;有以下三种方式:

(1)浮动元素脚底插入clear:both

注意:脚底插入的标签是要为block元素;

直接把<div style="clear:both;"></div>放到当作最后一个子标签放到父标签那儿,此方法兼容性强,使用方便,但是浪费了一个标签,而且只能使用一次,而且有时候如果不注意中间多了个空格会产生一段空白高度。

优点:通俗易懂,容易掌握;

缺点:可以想象通过此方法,会添加多少无意义的空标签,有违结构与表现的分离,在后期维护中将是噩梦;只是解决了浮动,但新添加的标签会与父元素外面的元素发生margin重叠;

(2)使用after伪对象清除浮动(与第一种方法原理相同)

该方法只适用于非IE浏览器,因为IE6/IE7不认识伪元素,具体写法可参照下面例;

.fix{zoom:1;}
.fix:after{
    display:block;
    content:'.';
    clear:both;
    line-height:0;
    visibility:hidden;
}

对于伪元素可以使用下面的方案,不用写太多东西:

.clearfix:after { content: ''; **display: table**; clear: both; }
.clearfix { *zoom: 1; }

使用中需注意以下几点:

a.该方法中必须为需要清除浮动元素的伪对象中设置 height:0,否则该元素会比实际高出若干像素;

b.content属性是必须的,但其值可以为空,蓝色理想讨论该方法的时候content属性的值 设为”.”,但我发现为空亦是可以的。

c.line-height:0写成height:0也是可以的。不会影响任何其他样式,通用性强,覆盖面广;

(3)BFC/haslayout解决

例如给包含浮动元素的父标签添加css属性.clearfix{overflow:hidden; zoom:1;}

其中zoom:1用于让IE6/IE7haslayout又不影响形体的神器。

此方法优点在于代码简洁,涵盖所有浏览器,可是对于overflow:hidden;要是里面的元素要是想来个margin负值定位或是负的绝对定位,岂不是直接被裁掉了,所以此方法是有不小的局限性的。

优点:不存在结构和语义化问题,代码量极少;父元素把子元素都包裹起来了,子元素不会与外层的元素发生margin重叠;

缺点:内容增多时候容易造成不会自动换行导致内容被隐藏掉,无法显示需要溢出的元素。

BFC/haslayout通常有以下声明:

  • float:left/right
  • position:absolute/fixed
  • overflow:hidden/scroll(IE7+)
  • display:inline-block/table-cell(IE8+)
  • width/height/zoom:1/…(IE6/IE7)

(4)总结:

此三种方法各有利弊,使用时应择优选择,比较之下第二种方法更为可取;

可以使用兼容性的解决方案:

.clearfix:after { // 解决IE8+
content: ''; 
display: block; 
height: 0; 
overflow: hidden; 
clear: both
}
.clearfix {*zoom: 1;} // 解决IE6/IE7

5.浮动的滥用

浮动的两大特性:元素block块状化(砌砖头)、破坏造成的紧密排列特性(去空格化)

block化即当元素增加浮动属性,其display属性转化为block;

去空格化是指当元素增加浮动属性,后面的空格&nbsp或者换行符\n都被移到后面了,实现了文字环绕效果;而换行符在标签的末尾没有任何表现,&nbsp可以选中;

砌砖布局的问题:

妙脆角-嘎吱脆; // 容错性比较糟糕,容易出问题

吝啬鬼-重用废; // 尺寸限定,模块到另外一个尺寸容器中,不配对,要重新整,完全没有重用性;

洋葱头-IE7飙泪; // 在低版本的IE有很多问题

6.float与流体布局

单侧固定(有两种方式):

width+float // 这一种是设置紧跟元素的width并设置为右浮动
padding-left/margin-left // 这一种是不设置width,只设置margin-left\
或者padding-left,让其自然适应

DOM与显示位置匹配的单侧固定布局:

width:100% + float // 外层
    padding-left/margin-left //设置margin-right,以防父元素的后面元素margin-left进父元素后,内容被父元素内容覆盖
width + float + margin负值 // 设置width和margin-left负值,使其进入上面元素内

高级进化 – 智能自适应尺寸(推荐):

float
display: table-cell; width: 2000px;          IE8+
*display: inline-block; *width: auto;       IE7-

7.浮动与兼容型

IE7的浮动问题:

  • 含clear的浮动元素包裹不正确的问题

  • 浮动元素倒数两个莫名垂直间距问题

  • 浮动元素最后一个字符重复问题

  • 浮动元素楼梯排列问题

  • 浮动元素和文本不在同一行的问题

CSS:网页布局

发表于 2015-08-13 | 分类于 css

三大布局:标准文档流、浮动布局、定位布局

标准文档流(flow)

  • 块级元素(block):从左到右撑满页面,独占行,从上到下

  • 内联元素、或行级元素(inline):行内显示,从左到右

  • 块级内联元素(inline-block):行内,但可以像块级元素一样编辑高度、宽度、行高以及顶和底边距

注:块级元素和行级元素都是盒子模型

浮动布局(float)

  • 设置了浮动的元素,仍旧处于标准文档流中

  • 左浮动、右浮动,对紧邻后面的元素有影响

  • 清除浮动:

    1. clear:both(left、right)
    2. 同时设置:width:100%(或固定宽度)+ overflow:hidden

定位布局(position)

三种定位方式:静态定位、相对定位、绝对定位

四个属性值:static、relative、absolute、fixed

  • 相对定位(relative)

    相对于自身位置进行偏移(原先位置仍然存在,对兄弟模块有影响,占了位置)、仍处于标准文档流中、随即拥有偏移属性和Z-index属性

  • 绝对定位(absolute)

    (1)建立了以包含块为基准的定位、完全脱离了标准文档流(对兄弟模块无影响)、随即拥有偏移属性和Z-index属性

    (2)未设置偏移量:无论是否存在已定位祖先元素,都保持在元素初始位置。

    (3)设置了偏移量:a.无已定位祖先元素,以为偏移参考基准;b.有已定位祖先元素,以距其最近的已定位祖先元素为偏移参考基准。

  • 绝对定位(fixed)

    (1) 与absolute定位类型类似,完全脱离了标准文档流(对兄弟模块无影响),但它的相对移动的坐标是视图(屏幕内的网页窗口)本身

    (2) 未设置偏移量时,有已定位祖先元素,以祖先元素为基准定位;无已定位元素,以浏览器窗口为基准定位;

    (3) 设置偏移量时,有、无已定位祖先元素,都以浏览器可视窗口为基准偏移;

    注意:上面设置偏移量的时候,可以根据需求设置,只设置top,只有top方向才会跟已定位的祖先元素偏移,left是不会相对已定位的祖先偏移的

    不过遇到一个问题一直想不明白,两个盒子box1、box2,box1在box2之前,并列关系,box1设置fixed之后,box2设置margin-top为50px,结果box1也跟着下来50px了????

山丘

发表于 2015-06-12 | 分类于 life

《山丘》

山丘

想说却还没说的 还很多

攒着是因为想写成歌

让人轻轻地唱着 淡淡地记着

就算终于忘了 也值了

说不定我一生涓滴意念

侥幸汇成河

然后我俩各自一端

望着大河弯弯 终于敢放胆

嘻皮笑脸 面对 人生的难

也许我们从未成熟

还没能晓得 就快要老了

尽管心里活着的还是那个

年轻人

因为不安而频频回首

无知地索求 羞耻于求救

不知疲倦地翻越 每一个山丘

越过山丘 虽然已白了头

喋喋不休 时不我予的哀愁

还未如愿见着不朽

就把自己先搞丢

越过山丘 才发现无人等候

喋喋不休 再也唤不回温柔

为何记不得上一次是谁给的拥抱

在什么时候

我没有刻意隐藏 也无意让你感伤

多少次我们无醉不欢

咒骂人生太短 唏嘘相见恨晚

让女人把妆哭花了 也不管

遗憾我们从未成熟

还没能晓得 就已经老了

尽力却仍不明白

身边的年轻人

给自己随便找个理由

向情爱的挑逗 命运的左右

不自量力地还手 直至死方休

越过山丘 虽然已白了头

喋喋不休 时不我予的哀愁

还未如愿见着不朽

就把自己先搞丢

越过山丘 才发现无人等候

喋喋不休 再也唤不回了温柔

为何记不得上一次是谁给的拥抱

在什么时候

越过山丘 虽然已白了头

喋喋不休 时不我予的哀愁

还未如愿见着不朽

就把自己先搞丢

越过山丘 才发现无人等候

喋喋不休 再也唤不回了温柔

为何记不得上一次是谁给的拥抱

在什么时候

喋喋不休 时不我予的哀愁

向情爱的挑逗 命运的左右

不自量力地还手 直至死方休

为何记不得上一次是谁给的拥抱

在什么时候

JS之旅(启程):对象

发表于 2015-05-24 | 分类于 js

下面代码都在node.js测试

这一次我们来谈谈对象。

什么是对象

对象是一种复合值。

对象是属性的无序集合,每个属性都是一个名/值对。

对象不仅仅是字符串到值的映射,除了可以保持自有的属性,还可以从原型对象继承属性。这种原型式继承是JS的核心特征。

对象是动态的,可以新增属性也可以删除属性。对象是可变,我们通过引用而非值来操作对象,如:

var x = {a:1, b:2};
var y = x;
y; // {a:1, b:2}
y.b = 3;
y; // {a:1, b:3}
x; // {a:1, b:3}

对象常见方法有:创建、设置、查找、删除、检测和枚举它的属性,接下来一一解析这些方法。

对象属性特性(property attribute):可写、可枚举、可配置。

对象自身还拥有三个相关的对象特性(attribute):原型、类、扩展标记。

对象分类:内置对象、宿主对象、自定义对象。

对象属性分类:自有属性、继承属性。

对象的KEY

对象的key为字符串,即使不为字符串,也会转换为字符串类型。如:

var obj = {};
obj[1] = 1;
obj['1'] = 2;
obj; // Object {1: 2}

obj[{}] = true; // {}会转换为字符串'[object Object]'
obj[{x : 1}] = true; // {x: 1}会转换为字符串'[object Object]'
obj; // Object {1: 2, [object Object]: true}

对象中不能存在两个同名的属性,如:

var obj = {a: 1, a: 2};
obj; // {a:2}

对象的结构

对象的属性有五个特征:

writable、enumerable、configurable、value、get/set

对象的创建

可以通过对象直接量、关键字new和Object.create()函数来创建。

字面量创建

var obj1 = {x: 1, y: 2};

在ES5中,保留字可以用作不带引号的属性名;而ES3使用保留字作为属性名必须使用引号引起来。

在ES5中,对象直接量最后一个属性后的逗号将忽略;且在ES3的大部分实现中也可以忽略这个逗号,而在IE中则报错。

注意:所有通过对象直接量创建的对象都具有同一个原型,原型指向Object.prototype

new以及原型链

通过关键字new和构造函数调用创建的对象的原型就是构造函数的prototype属性的值。通过new Array()创建的对象的原型就是Array.prototype,通过new Date()创建的对象原型就是Date.prototype。

所有的内置构造函数(以及大部分自定义的构造函数)都具有一个继承自Object.prototype的原型。因此由new Date()创建的对象同时继承Array.prototype和Object.prototype。这一系列链接的原型对象就是所谓的原型链。

Object.create() ES5定义

直接在花括号里传入所需的原型对象即可:

var obj = Object.create({x : 1});
obj.x // 1
typeof obj.toString // "function"
obj.hasOwnProperty('x');// false

可以通过传入参数null来创建一个没有原型的新对象,但通过这种方法创建的对象不会继承任何东西,甚至包括基础方法,如:

var obj = Object.create(null);
obj.toString // undefined,注意与上面例子的比较

注意:obj原型为{x:1},而{x:1}原型为Object.prototype,所以obj可以访问到toString方法

如果想通过Object.create()来创建一个空对象,可以通过下面方法来创建:(也可以通过{}和new Object()来创建)

Object.create(Object.prototype)

ES3模拟原型继承,不过inherit不能完全替代Object.create(),因为前者不能传入null来创建对象,也不能接受可选的第二个参数,下面为inherit的实现:

function inherit(p){
    if(p == null){
        throw TypeError();
    }
    if(Object.create){
        return Object.create(p);
    }
    var t = typeof p;
    if (t !== "object" && t !== "function"){
        throw TypeError();
    }
    function f(){};
    f.prototype = p;
    return new f();
}

对象方法

属性的查询和设置

可以通过.运算符或者方括号[]访问属性。对于.运算符来说,右侧必须是一个以属性名称命名的简单标识符;对于方括号来说,方括号内必须是一个计算结果为字符串的表达式(严格地讲,表达式必须返回字符串或返回一个可以转换为字符串的值),如:

var obj = {'5': "abc", '7': "cdf", c: 100};
obj.c; // 100
obj['c']; // 100
obj[c]; // Error:c is not defined
obj[1]; //  undefined,因为1会转换为字符串'1'
obj['5']; // 'abc'
obj[1+4]; // 'abc'

在ES3中,点.后面的标识符不能是保留字,ES5放宽了,可以直接在.运算符后面直接使用保留字。

关联数组的对象

使用.运算符来访问对象的属性时,属性名用一个标识符来表示,由于标识符不是数据类型,因此程序无法修改它们。

而通过[]来访问时,属性名通过字符串来表示,在程序运行时,可以修改和创建他们,如:

var addr = ""
for(i=0; i<4; i++){
    addr += customer["address" + i] + '\n';
}

可以看见关联数组的方式来实现更强大、更灵活,推荐。

继承

属性访问细节:

假如要查询对象o的属性x,如果o中不存在x,那么将会继续在o的原型对象中查询属性x。如果原型对象中也没有x,但这个原型也有原型,那么继续在这个原型对象的原型上执行查询,直到找到x或者查找到一个原型是null的对象为止。如:

var o = {};
o.x = 1;
var p = Object.create(o);
p.y = 2;
var q = Object.create(p);
q.z = 3;
q.x + q.y //3 ,x和y分别继承自o和p

属性赋值细节:

1.假如对象o已经有属性x(这个属性不是继承来的),那么这个赋值操作只改变这个已有属性x的值。

2.如果o不存在x,接着判断:

(1)现在如果原型上有属性x且为可写,则会为o创建一个x属性;

(2)如果原型上有属性x且不可写,则赋值失败;

如:

var obj1 = {};
Object.defineProperty(obj1, 'z', {value:1, writable:false, enumerable: true, configurable:true});
obj1.z; // 1
Object.keys(obj1); // ['z']
obj2 = Object.create(obj1);
obj2.z; // 1
obj2.z=2; // 赋值失败,则有下面的结果
obj2.z; // 1
obj2.hasOwnProperty('z'); // false
Object.defineProperty(obj1, 'z', {value:1, writable:true, enumerable: true, configurable:true});
obj2.z=2; // 赋值成功,则有下面的结果
obj2.z; // 2
obj2.hasOwnProperty('z'); // true

注意:如果原型上有属性x,而这个属性是一个具有setter方法的accessor属性,那么这时将调用setter方法而不是给o创建一个属性x。且setter是对象o调用的,因此setter方法的操作只是针对o本身,并不会修改原型链。(此处例子见下面set/get介绍)

注意:只有在查询属性时才会体会到继承的存在,而设置属性则和继承无关。

属性访问错误

查询一个不存在的属性并不会报错,如:

var obj = {x : 1};
obj.y; // undefined

但是,如果对象不存在,那么试图查询这个不存在的对象的属性就会报错,如:

var yz = obj.y.z; // TypeError: Cannot read property 'z' of undefined
obj.y.z = 2; // TypeError: Cannot set property 'z' of undefined

处理方式:

var yz;
    if (obj.y) {
    yz = obj.y.z;
}

更简练的方式,可以使用&&的短路行为:

var yz = obj && obj.y && obj.y.z

给null和undefined设置属性也会报类型错误。设置属性的失败操作都会抛出一个类型错误异常,除了一些对象例外,如:

Object.prototype = 1; 
Object.prototype // {},赋值失败,但上面没有报错

这是一个历史遗留bug,在严格模式下,任何设置属性的失败操作都会抛出一个类型错误异常。

属性的删除

delete可以删除对象的属性,如

var person = {age : 28, title : 'fe'};
delete person.age; // true
delete person['title']; // true
person.age; // undefined
delete person.age; // true

需要注意,delete只是断开属性和宿主对象的联系,而不会去操作属性中的属性,由于已经删除的属性的引用依然存在,可能因为这种不严谨的代码而造成内存泄露,如

var a = {p: {x:1}};
var b = a.p;
b; // {x:1}
delete a.p;
b; // {x:1}

delete只能删除自有属性,而不能删除继承属性。

delete不能删除那些可配置性为false的属性,在严格模式下,删除一个不可配置属性会报一类型错误,在非严格模式下,这些情况下的delete操作会返回一个false:

delete Object.prototype; // false,

var descriptor = Object.getOwnPropertyDescriptor(Object, 'prototype');
descriptor.configurable; // false

另外,用var定义的全局变量或者局部变量也不能删除,全局函数或者局部函数也不能删除,如:

var globalVal = 1;
delete globalVal; // false

(function() {
       var localVal = 1;
    return delete localVal;
}()); // false


function fd() {}
delete fd; // false

(function() {
    function fd() {};
    return delete fd;
}()); // false

隐式定义的全局变量可以删除,如

ohNo = 1;
ohNo; // 1
delete ohNo; // true

注意:属性的删除由属性的特性configurable值来控制。

属性的检测

三种方法:in运算符、hasOwnProperty(是否是自有属性)、propertyIsEnumerable(检测是否为自有属性且可枚举),如:

var cat = new Object;
cat.legs = 4; // leg属性自有且可枚举
Object.defineProperty(cat, 'age', {value:1, writable:true, enumerable: false, configurable:true}); // age属性为自有但不可枚举

'legs' in cat; // true
'age' in cat; // true
'abc' in cat; // false
"toString" in cat; // true, inherited property!!!

cat.hasOwnProperty('legs'); // true
cat.hasOwnProperty('age'); // true
cat.hasOwnProperty('toString'); // false

cat.propertyIsEnumerable('legs'); // true
cat.propertyIsEnumerable('age'); // false
cat.propertyIsEnumerable('toString'); // false

Object.keys(cat); //legs

属性设置方法(用defineProperty设置的属性默认是不可枚举):

Object.defineProperty(cat, 'price', {value : 1000});
Object.getOwnPropertyDescriptor(cat, 'price'); // {value: 1000, writable: false, enumerable: false, configurable: false}
cat.propertyIsEnumerable('price'); // false
cat.hasOwnProperty('price'); // true

除了使用in运算符外,另一种更简单的方法是使用“!==”判断一个属性是否是undefined,但当一个属性值刚好为undefined时无效,这时用in运算符才准确,如:

var o = {x:1}
o.x !== undefined; // true
o.y !== undefined; // false

var o = {x: undefined}
o.x !== undefined; // false
o.y !== undefined; // false
"x" in o; // true
"y" in o; // false

注意:!=不是严格等于,undefined与null相等,所以有下面的:

if (cat.legs != undefined) {
    // !== undefined, or, !== null
}

属性的枚举

对象继承的内置方法不可枚举,但在代码中给对象添加的属性都是可枚举的(除了defineProperty这类情况),见例子:

var o = {x : 1, y : 2, z : 3};
'toString' in o; // true
o.propertyIsEnumerable('toString'); // false
var key;
for (key in o) {
    console.log(key); // x, y, z
}

for..in..会去原型链上枚举所有特征Enumerable为true的属性,如:

var obj = Object.create(o);
obj.a = 4;
var key;
for (key in obj) {
    console.log(key); // a, x, y, z
}

只枚举出是自己属性的:

var obj = Object.create(o);
obj.a = 4;
var key;
for (key in obj) {
    if (obj.hasOwnProperty(key)) {
        console.log(key); // a
    }
}

ES5增加了两个用于枚举属性名称的函数,Object.keys()和Object.getOwnPropertyNames(),如:

Object.defineProperty(obj, 'b', {value:1}) // 属性b不可枚举
Object.keys(obj); // ['a'],枚举出特性为可枚举的自有属性
Object.getOwnPropertyNames(obj); // ['a', 'b'],所有自有属性,不管特性是否可枚举

get、set方法

对象的属性分为数据属性和存取器属性,存取器属性就是属性值可以用一个或两个方法替代,这两个方法就是setter、getter

读取存取器属性的值是,调用getter方法(无参数),这个方法的返回值就是存取器属性的值;当程序设置一个存取器属性的值时,JS调用setter方法,将赋值表达式右侧的值当做参数传入setter,可以忽略setter的返回值。

读取只写属性总是返回undefined,如:

var o = { set a(value){}};
o.a; // undefined

先看一个例子:

var man = {
    name : 'cauil',
    weibo : '@cauil',
    get age() {
        return new Date().getFullYear() - 1989;
    },
    set age(val) {
        console.log('Age can\'t be set to ' + val);
    }
}

console.log(man.age); // 
man.age = 100; // Age can't be set to 100
console.log(man.age); // still 27

再来看一个例子:

var man = {
    weibo : '@cauil',
    $age : null,
    get age() {
        if (this.$age == undefined) {
            return new Date().getFullYear() - 1989;
        } else {
            return this.$age;
        }
    },
    set age(val) {
        val = +val;
        if (!isNaN(val) && val > 0 && val < 150) {
            this.$age = val;
        } else {
            throw new Error('Incorrect val = ' + val);
        }
    }
}

console.log(man.age); // 27
man.age = 100;
console.log(man.age); // 100;
man.age = 'abc'; // error:Incorrect val = NaN

get/set与原型链, 本身对象属性不存在时,会去原型上查找,尝试赋值时,会去读取原型链上的set方法,是原型调用:

function foo(){};

Object.defineProperty(foo.prototype, 'z', 
    {get: function(){return 1;}});
var obj = new foo();

obj.z; // 1
obj.z = 10;
obj.z; // still 1

Object.defineProperty(obj, 'z', 
{value : 100, configurable: true});
obj.z; // 100;
delete obj.z;
obj.z; //back to 1

如果o继承自一个只读属性x,那么赋值操作是不允许的,如果允许属性赋值,它也只是在原始对象上创建属性或对已有的属性赋值,而不会去修改原型链,如:

var o = {};
Object.defineProperty(o, 'x', {value : 1}); // writable=false, configurable=false
var obj = Object.create(o);
obj.x; // 1
obj.x = 200;
obj.x; // still 1, can't change it

Object.defineProperty(obj, 'x', {writable:true, configurable:true, value : 100});
obj.x; // 100
obj.x = 500;
obj.x; // 500

属性特性

数据属性具有四个特性:value、writable、enumerable、configurable

存取器属性具有四个特性:set、get、enumerable、configurable

Object.getOwnPropertyDescriptor()

可以获取某个对象特定属性的属性描述符,如:

var o = {x:1, get y(){}, set y(value){}}
// { value: 1, writable: true, enumerable: true, configurable: true }
Object.getOwnPropertyDescriptor(o, "x"); 

// { get: [Function: y], set: [Function: y], enumerable: true, configurable: true } 
Object.getOwnPropertyDescriptor(o, "y");

对于不存在和继承的属性,返回undefined,如:

Object.getOwnPropertyDescriptor(o, "z"); // undefined
Object.getOwnPropertyDescriptor(o, "toString"); // undefined

Object.defineProperty()

此方法是设置属性的特性,或者让新建对象具有某种特性,使用这种方法设置的属性,如果没有定义的特性,则默认为false,先看一个例子:

Object.getOwnPropertyDescriptor({pro : true}, 'pro');
// Object {value: true, writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptor({pro : true}, 'a'); // undefined

再看一个例子:

var person = {};
Object.defineProperty(person, 'name', {
    configurable : false,
    writable : false,
    enumerable : true,
    value : "Bosn Ma"
});

person.name; // Bosn Ma
person.name = 1;
person.name; // still Bosn Ma
delete person.name; // false,因为name特性为不可配置

Object.keys()

Object.keys(person)获取person的所有可枚举自有属性,如:

Object.defineProperty(person, 'type', {
    configurable : true,
    writable : true,
    enumerable : false,
    value : "Object"
});

Object.keys(person); // 只有["name"]

Object.defineProperties()定义多个对象

Object.defineProperties(person, {
    title : {value : 'fe', enumerable : true},
    corp : {value : 'BABA', enumerable : true},
    salary : {value : 50000, enumerable : true, writable : true}
});

Object.getOwnPropertyDescriptor(person, 'salary');
// Object {value: 50000, writable: true, enumerable: true, configurable: false}

特征定义

configurable主要控制是否可以delete、是否可以修改get/set方法,是否可以修改特征值(如writable和configurable)的属性。

writable主要控制属性值是否可以修改值。

注意:有一个特例,当configurable为false时,writable可以从true修改为false,其他情况都不能配置特征的值。

对象本身特征与序列化(待整理修改)

原型

获取对象的原型,使用Object.getPrototypeOf(),只会返回原型,不会返回原型链,如

var o = { x:1, y:2};
var p = new Array();
var q = Object.create(o);

Object.getPrototypeOf(o); // {}
Object.getPrototypeOf(p); // []
Object.getPrototypeOf(q); // {x:1, y:2}

判断x是否为y的原型,使用x.isPrototypeof(y)

var o = {x:1}
var p = Object.create(o);

o.isPrototypeOf(p); // true
Object.prototype.isPrototypeOf(p); // true

class

对象的类属性是一个字符串,用以表示对象的类型信息,如:

var toString = Object.prototype.toString;
function getType(o){return toString.call(o).slice(8,-1);};


toString.call(null); // "[object Null]"
getType(null); // "Null"
getType(undefined); // "Undefined"
getType(1); // "Number"
getType(new Number(1)); // "Number"
typeof new Number(1); // "object"
getType(true); // "Boolean"
getType(new Boolean(true)); // "Boolean"

其中new Number(1)为Object Number

extensible

Object.preventExtensions会设置对象是否不可扩展,如:

var obj = {x : 1, y : 2};
Object.isExtensible(obj); // true
Object.preventExtensions(obj);
Object.isExtensible(obj); // false
obj.z = 1;
obj.z; // undefined, add new property failed
Object.getOwnPropertyDescriptor(obj, 'x');
// Object {value: 1, writable: true, enumerable: true, configurable: true}

Object.seal会在preventExtensions基础上添加configurable:false特征,如:

Object.seal(obj);
Object.getOwnPropertyDescriptor(obj, 'x');
// Object {value: 1, writable: true, enumerable: true, configurable: false}
Object.isSealed(obj); // true

Object.freeze会在preventExtensions基础上添加configurable:false,writable:false特征,如:

Object.freeze(obj);
Object.getOwnPropertyDescriptor(obj, 'x');
// Object {value: 1, writable: false, enumerable: true, configurable: false}
Object.isFrozen(obj); // true

序列化

对象系列化可以使用JSON.stringfy(obj),如:

var obj = {x : 1, y : true, z : [1, 2, 3], nullVal : null};
JSON.stringify(obj); // "{"x":1,"y":true,"z":[1,2,3],"nullVal":null}"

序列化的一个属性值为undefined时,会忽略;时间对象会转化为UTC格式;NaN/infinity会转化为null,如:

obj = {val : undefined, a : NaN, b : Infinity, c : new Date()};
JSON.stringify(obj); // "{"a":null,"b":null,"c":"2015-01-20T14:15:43.910Z"}"

JSON转化为javascipt对象,需要用引号引起来,如:

obj = JSON.parse('{"x" : 1}');
obj.x; // 1

自定义:

var obj = {
    x : 1,
    y : 2,
    o : {
        o1 : 1,
        o2 : 2,
        toJSON : function () {
            return this.o1 + this.o2;
        }
    }
};
JSON.stringify(obj); // "{"x":1,"y":2,"o":3}"

定义自己对象上的toString方法和valueOf方法,如:

var obj = {x : 1, y : 2};
obj.toString(); // "[object Object]"
obj.toString = function() {return this.x + this.y};
"Result " + obj; // "Result 3", by toString

+obj; // 3, from toString

obj.valueOf = function() {return this.x + this.y + 100;};
+obj; // 103, from valueOf

"Result " + obj; // still "Result 3"

如果运算的对象的valueof和toString都存在,当进行一元‘+’和二元‘+’运算的时候,会进行转换字符串的操作;

如果valueOf方法存在返回的是基本类型的值,以valueOf的值为结果,反之,valueOf不存在或者返回的是对象,则会去找toString方法,如果两者都不存在或者返回对象,就会报错。

不过一般都会继承Object.prototype.toString的方法。

伦敦威斯敏斯特教堂一墓志铭

发表于 2015-04-16 | 分类于 读书

在伦敦威斯敏斯特教堂旁,矗立着一块墓碑上书:

“当我年轻时,我梦想改变这个世界;
当我成熟后,我发现我不能改变这个世界,我决定只改变我的国家;
当我进入暮年,我发现我不能改变我们的国家,我的最后愿望仅仅是改变一下我的家庭,但是,这也不可能。

当我现在躺在床上,行将就木,我突然意识到:如果一开始我仅仅去改变我自己,然后,我可能改变我的家庭;
在家人的帮助和鼓励下,我可能为国家做一些事情;
然后,谁知道呢?我甚至可能改变这个世界。”

1…34
cauil

cauil

37 日志
12 分类
23 标签
GitHub Weibo DouBan
© 2018 cauil
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.3