xml地图|网站地图|网站标签 [设为首页] [加入收藏]
css属性的选择对动画性能的影响,前端开发基础
分类:web前端

聊聊Clip-Path

2016/09/19 · 基础技术 · clip-path

本文作者: 伯乐在线 - Damonare 。未经作者许可,禁止转载!
欢迎加入伯乐在线 专栏作者。

css属性的选择对动画性能的影响

2016/03/06 · CSS · 1 评论 · 动画

原文出处: 携程设计委员会   

现在手机的占比越来越高,各种酷炫页面层出不穷,这些特效都离不开css动画。说到css动画,主流的情况也就无非这两大类:位移和形变。而我们在写一个动画特效的过程中,如何去提升它的性能呢?当然首先我们需要去了解一下基本的概念,比如浏览器渲染的工作原理等,这些我也在读了几位大牛写的相关文章后才有了一定的了解,这边我也不细说了,有兴趣的同学可以去了解一下。本次的目的简单粗暴地讲,其实就是我们应该使用什么css属性去进行动画的绘制时,能够有效的提高浏览器在渲染和绘制过程中的性能。

快速进入主题,大家先来看个demo。我分别使用了left和transform在2秒内向右平移了500px的位移。代码如下:

<style> .box-ps,.box-tf{position:absolute;top:0;left:0;width:100px;height:100px;background-color:red;} .box-ps{-webkit-animation:box-ps 2s linear;} .box-tf{-webkit-animation:box-tf 2s linear;} @-webkit-keyframes box-ps{ 0%{ left:0; }100%{ left:500px; } } @-webkit-keyframes box-tf{ 0%{ -webkit-transform:translate(0,0); }100%{ -webkit-transform:translate(500px,0); } } </style> <body> <div class="box-ps"></div> <div class="box-tf"></div> </body>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<style>
.box-ps,.box-tf{position:absolute;top:0;left:0;width:100px;height:100px;background-color:red;}
.box-ps{-webkit-animation:box-ps 2s linear;}
.box-tf{-webkit-animation:box-tf 2s linear;}
 
@-webkit-keyframes box-ps{
0%{
left:0;
}100%{
left:500px;
}
}
 
@-webkit-keyframes box-tf{
0%{
-webkit-transform:translate(0,0);
}100%{
-webkit-transform:translate(500px,0);
}
}
</style>
 
<body>
<div class="box-ps"></div>
<div class="box-tf"></div>
</body>

然后在chrome下得到了如下的结果,第一张为使用left的截图,第二张为使用transform的截图:

澳门新葡亰手机版 1

澳门新葡亰手机版 2

显而易见,我们在帧模式这里可以看到left比transform帧数要低,而且在渲染和绘制这边的耗时,left要远远的大于transform。看到这里,相信大家心里已经有结论了。
我们再利用chrome的show paint rectangles来观察一下两者在动画过程中,渲染和绘制的区域有何差异,第一张为使用left的截图,第二张为使用transform的截图:

澳门新葡亰手机版 3

澳门新葡亰手机版 4

我们可以看到,使用left写的整个动画过程中,浏览器一直在进行绘制处理。而相对而言,使用transform时,仅仅是在动画开始和结束是进行了绘制。因此,对于动画的性能上,transform要更为出色。至于原因,这里就要引入一个触发重新布局的概念:

我们在改变一些属性时,如果是跟layout相关的属性,则会触发重新布局,导致渲染和绘制所需要的时间将会更长。因此,我们在写动画的时候因该规避这些属性:width, height, margin, padding, border, display, top, right, bottom ,left, position, float, overflow等。
不会出发重新布局的属性有:transform(其中的translate, rotate, scale), color, background等。

所以,我们平时在写css动画时,应该优先使用不触发重新布局的属性,这样可以使我们所展示动画效果的更加流畅。

 

2 赞 3 收藏 1 评论

澳门新葡亰手机版 5

前端开发基础-JavaScript

2016/03/22 · CSS · Javascript, 前端基础

原文出处: icepy   

这是很久很久之前想写的东西,拖了五六个月,没有动笔,现今补齐,内容有些多,对初学者有用,错误之处,望指出。

前言

图片是一个网站必不可少的元素,而呈现出绚丽多彩的图片效果在很多情况下不仅仅是设计师的工作,通过代码来修饰图片也是一个前端工程师必备的技能。因为兼容性的问题,实际项目中可能用的比较少,包括博主自己也只是用过几次剪切,很多情况下都交给设计师去做了。但作为一个hacker怎么能满足于此呢,必须深入探究!

理解作用域

理解作用域链是Js编程中一个必须要具备的,作用域决定了变量和函数有权力访问哪些数据。在Web浏览器中,全局执行环境是window对象,这也意味着所有的全局变量或者方法都是window对象的属性或方法。当一个函数在被调用的时候都会创建自己的执行环境,而这个函数中所写的代码就开始进入这个函数的执行环境,于是由变量对象构建起了一个作用域链。

JavaScript

var wow = '魔兽世界'; var message = function(){ var _wow = '123'; }

1
2
3
4
var wow = '魔兽世界';
var message = function(){
    var _wow = '123';
}

在这个例子中全局环境中包含了两个对象(全局环境的变量对象不算),window.wow和window.message,而这个message函数中又包含了两个对象,它自己的变量对象(其中定义了arguments对象)和全局环境的变量对象。当这个函数开始执行时,message自己的变量对象中定义了_wow,而它的全局环境的变量对象有wow,假设在message中alert一下wow,实际上是message中包含的全局环境的变量对象.wow,于是可以访问。

JavaScript

var wow = '123'; var message = function(){ var wow = '456'; }

1
2
3
4
var wow = '123';
var message = function(){
    var wow = '456';
}

如果执行message函数alert一下wow,它的作用域是这样开始搜索的,先搜索message自己的变量对象中是否存在wow,如果有就访问并且立马停止搜索,如果没有则继续往上访问它,有wow,则访问并且立马停止搜索,以此类推一直搜索到全局环境上的变量对象,如果这里都没,恭喜你,这里要抛错了。

JavaScript

var c = '123'; var message = function(){ var g = '123'; var a = function(){ var d = '123'; } }

1
2
3
4
5
6
7
var c = '123';
var message = function(){
    var g = '123';
    var a = function(){
        var d = '123';
    }
}

在这个例子中包含有三个执行环境,全局环境,message的环境,a的环境。从这里可以看出message自身包含两个对象,自己的变量对象和全局环境中的变量对象,而函数a则包含了三个,自身的变量对象,message的变量对象和全局变量对象。

当开始执行这个函数时,在函数a中可以访问到变量g,那是因为函数a包含了message的变量对象,于是在自身没有开始搜索上一级的变量对象时发现了,于是可以访问。那么访问c的原理也是如此,当自身和上一级的message的变量对象都没有,但是全局变量对象中存在,于是访问成功。

澳门新葡亰手机版,了解这个作用域,对于Js编程是至关重要的,不然可能会出现,明明想要的预期结果是123,但是变成了456,为什么?那就是因为一级一级的搜索,可能会存在覆盖,或者搜索到别的地方就立即停止搜索了。

正文

理解引用类型

引用类型虽然看起来和类很相似,但是它们却是不同的概念,引用类型的值,也就是对象是引用类型的一个实例。在Js中引用类型主要有Object,Array,Date,正则,Function等。

Object和Function在后面详细复述。

Array

在Js中数组可以存储任意的数据,而且它的大小是可以动态调整的类似于OC中的NSMutableArray。创建数组可以使用构造函数的方式也可以使用字面量的形式,另外可以使用concat从一个数组中复制一个副本出来。数组本身提供了很多方法让开发者使用来操作数组。

  • length 数组的长度
  • toString 可以返回一个以,拼接的字符串,相当于是调用了下join(‘,’)
  • join 可以用一个分割符来拼接成一个字符串
  • push 添加一个数据到数组的末端
  • pop 删除数组中的最后一项,有返回值
  • shift 删除数组的第一项,有返回值
  • unshift 添加一个数据到数组的首端
  • reverse 倒序
  • sort 可以传入一个排序的函数
  • slice 可以基于当前数组返回一个新的数组,接收两个参数,返回项的起始位置和结束位置
  • splice 可以传入N个参数,第一个参数表示要删除,插入或则替换的位置,第二个参数表示要删除的项数,第三个到第N个表示要插入或则替换的数据

Date

时间对象也是使用非常多的玩意,它是使用GMT时间来描述,而且时间对象是可以直接比对大小的。

JavaScript

var date1 = new Date(2015,1,2); var date2 = new Date(2015,1,10); date1 < date2

1
2
3
var date1 = new Date(2015,1,2);
var date2 = new Date(2015,1,10);    
date1 < date2

常用的方法

  • getTime 获取时间对象的毫秒数
  • setTime 设置时间对象的毫秒数,会改变日期
  • getFullYear 获取时间对象的年(2015)
  • getMonth 获取时间对象的月(需要加1)
  • getDay 获取日期的星期几(0-6)星期天到星期六
  • getDate 获取日期的天数
  • getHours 获取当前日期的小时
  • getMinutes 获取当前日期的分钟数
  • getSeconds 获取当然日期的秒数

上面看起来都是获取,当然也有设置,只是相应的get置换成set即可。

正则表达式

在Js里正则表达式是用RegExp类型来支持的,关于正则可以看看之前写的一篇文章,用python来描述的如何读懂正则。

Js也支持三种模式,gim,表示全局,不区分大小写,多行。

一般来说很少有人这么使用var xxx = new RegExp(),而是用字面量的方式,比如var xx = /[bc]/gi;像用的比较多的方法有exec用于捕获包含第一个匹配项的数组,没有则返回null。test,用于判断,如果匹配返回true,不匹配返回false。

处理字符串

在Js中还有一种叫做包装类型的玩意,正因为此所以处理一些基本数据类型,比如字符串时,有很多方法可以使用。

  • concat 可以将一个或者多个字符串拼接起来,返回一个新的字符串
  • slice 接收两个参数,起始位置和结束位置,返回一个新的字符串
  • substr和substring和slice一样,唯一的不同是substr第二个参数是返回字符串的个数
  • indexOf 从头开始查询字符串,存在会返回它所在的位置,没有返回-1
  • lastIndexOf 从最后开始查询字符串
  • toUpperCase 转大写
  • toLowerCase 转小写
  • match 正则表达式使用跟exec一样
  • search 正则表达式使用,查询到返回一个位置,没有返回-1
  • replace 替换,第一个参数可以是正则表达式也可以是字符串,第二个参数是要替换的字符串
  • localeCompare比较字符串,如果字符串相等返回0,如果字符串的字母排在参数字符串之前,返回负数,如果是之后,返回正数。

Flilter

filter有十种特效来处理图片,博主只放几种特效的样例给大家看一下:

照片反色效果:

澳门新葡亰手机版 6

照片褐色效果:

澳门新葡亰手机版 7

照片阴影效果:

澳门新葡亰手机版 8

十种特效源码:

-webkit-filter:opacity(.6);//透明度 filter:opacity(.6); -webkit-filter:blur(10px);//照片模糊 filter:blur(10px); -webkit-filter:invert(1); filter:invert(1); -webkit-filter:saturate(3);//照片饱和度 filter:saturate(3); -webkit-filter:grayscale(1);//照片灰度 filter:grayscale(1); -webkit-filter:sepia(1);//照片褐色 filter:sepia(1); -webkit-filter:hue-rotate(90deg);//色相旋转 filter:hue-rotate(90deg); -webkit-filter:brightness(.5);//亮度 filter:brightness(.5); -webkit-filter:contrast(2);//对比度 filter:contrast(2); -webkit-filter:drop-shadow(10px 10px 10px #ccc);//阴影 filter:drop-shadow(10px 10px 10px #ccc);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-webkit-filter:opacity(.6);//透明度
filter:opacity(.6);
-webkit-filter:blur(10px);//照片模糊
filter:blur(10px);
-webkit-filter:invert(1);
filter:invert(1);
-webkit-filter:saturate(3);//照片饱和度
filter:saturate(3);
-webkit-filter:grayscale(1);//照片灰度
filter:grayscale(1);
-webkit-filter:sepia(1);//照片褐色
filter:sepia(1);
-webkit-filter:hue-rotate(90deg);//色相旋转
filter:hue-rotate(90deg);
-webkit-filter:brightness(.5);//亮度
filter:brightness(.5);
-webkit-filter:contrast(2);//对比度
filter:contrast(2);
-webkit-filter:drop-shadow(10px 10px 10px #ccc);//阴影
filter:drop-shadow(10px 10px 10px #ccc);

但实际上这个属性兼容性很低:

澳门新葡亰手机版 9

截止博主发文日期,Filter的兼容性如上图,我们可以看到IE是完全不支持的,Edge也是部分支持。这可能也是Filter没法用在项目中的原因之一了。感兴趣的读者可以Copy博主代码本地测试一下,或是参照MDN|Filter了解。博主不在这里做过多的说明了。

函数

  • Function

说起来Js的核心是什么?那就是函数了。对于函数主要是理解它的几个概念。

  • 它可以当值来传递,没有重栽。
  • 声明的时候,比如function a(){} var a = function(){} 执行时会有区别
  • 函数内部的参数arguments包含了传入的所有参数
  • this,表示在这个函数内的作用域,以及prototype

clip&clip-path

这两个属性正是今天的重头戏,博主曾在从隐藏元素谈起提起过,但并没做深入解释。是的,它可以用来隐藏元素,当然也就能处理图片了。

  • clip

clip这个属性我相信会有很大一部分人不知道,因为这个属性使用率非常的低,因为很多情况下我们会直接重新切一张新图出来代替而不会去剪裁已有的图片,但实际上这个属性用在CSS sprite简直就如同神器一般,因为在很多情况下background-position并不符合我们的需求,比如,有时我们希望Sprite图片可以延迟滚动加载,或者是可以很轻松的右键图片另存为…或是其它background-position没法满足的情景。
废话不多说,看样例:
澳门新葡亰手机版 10

position:absolute; clip:rect(50px 250px 250px 50px); /* IE6, IE7 */ clip:rect(50px,250px,250px,50px);

1
2
3
position:absolute;
clip:rect(50px 250px 250px 50px); /* IE6, IE7 */
clip:rect(50px,250px,250px,50px);

澳门新葡亰手机版 11

注意,元素定位position必须是absolute或是fixed的,兼容IE6,IE7需要将值之间的逗号去掉。另外,react(top,right,bottom,left);四个值分别是相对于图片左上角为原点的坐标值。Clip基本所有的浏览器都支持,可以放心使用。

让人放弃它的原因无外乎:

  • clip 只对绝对定位的元素有效对于position:relative和position:static无效
  • clip 只能用于矩形,即rect()函数
  • clip-path

其实clip在HTML5中已经被废弃了(依然可用),取而代之的是clip-path。本来clip还有一个circle(圆),但基本没有浏览器实现这个属性值,只有rect()可是使用,可能W3C也是等不下去了吧,直接推出了一个更牛逼的属性——clip-path,这个属性起初是SVG里面的然后被挪用到了CSS里面。关于SVG博主有时间会再另外介绍,这里按下不表。效果图:
澳门新葡亰手机版 12
澳门新葡亰手机版 13

读者可以在这里自行体验

兼容:现在为止IE 和 Edge 不支持这个属性,Firefox 仅部分支持 clip-path ,
Chrome、Safari 和 Opera 需要使用 -webkit- 前缀支持此属性。
澳门新葡亰手机版 14
clip-path兼容性甚至比前面说到的filter还差,所以很难真正使用起来。更多使用效果戳这里和这里

说一下它的四个属性值:

  • clip-source: 可以是内、外部的SVG的clipPath元素的URL引用;
  • basic-shape: 使用一些基本的形状函数创建的一个形状。主要包括circle()、ellipse()、inset()和polygon()。
  • geometry-box: 是可选参数。此参数和basic-shape函数一起使用时,可以为basic-shape的裁剪工作提供参考盒子。如果geometry-box由自身指定,那么它会使用指定盒子形状作为裁剪的路径,包括任何(由border-radius提供的)的角的形状。

开始使用clip-path

在开始使用clip-path绘制图形,或者说裁剪图形之前,有两点需要大家注意:

  • 使用clip-path要从同一个方向绘制,如果顺时针绘制就一律顺时针,逆时针就一律逆时针,因为polygon是一个连续线段,若线段彼此有交集,裁剪区域就会有相减的情况发生,当然如果你特意需要这样的效果除外。
  • 如果绘制时采用比例的方式绘制,长宽就必须要先行设定,不然有可能绘制出来的长宽和我们想像的就会有差距,使用像素绘制就不会有这样的现象。

我们就拿上面途中的六边形作为polygon()函数示例:

-webkit-clip-path: polygon(0% 50%, 25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%); clip-path: polygon(0% 50%, 25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%);

1
2
-webkit-clip-path: polygon(0% 50%, 25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%);
clip-path: polygon(0% 50%, 25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%);

效果图:

澳门新葡亰手机版 15

讲解:

澳门新葡亰手机版 16

每个点的第一个坐标值决定了它在 x 轴上的位置,第二个坐标值指定了它在 y 轴的位置,所有点是顺时针绘制的。其实一个 polygon()就能满足所有的形状需要了,有自定义的API用更加方便不是么。

注意:inset()这个真的坑,按说同样裁剪成方形应该是和clip的rect一样用法,可不一样!

 

//Clip的rect position:absolute; clip:rect(50px 250px 250px 50px); //clip-path clip-path: inset(50px 50px 50px 50px); -webkit-clip-path: inset(50px 50px 50px 50px);

1
2
3
4
5
6
//Clip的rect
position:absolute;
clip:rect(50px 250px 250px 50px);
//clip-path
clip-path: inset(50px 50px 50px 50px);
-webkit-clip-path: inset(50px 50px 50px 50px);

本文使用图片是300*300的。

很明显:

 

clip:rect(50px 250px 250px 50px); =clip-path: inset(50px 50px 50px 50px);

1
2
clip:rect(50px 250px 250px 50px);
=clip-path: inset(50px 50px 50px 50px);

好大的一个坑….

理解匿名函数和闭包

匿名函数又叫拉姆达函数,主要是在把函数当值传递的时候用,或者是把函数当返回值,比如:

JavaScript

function d(callback){ callback(); } d(function(){ alert('123') }); //或者 function b(){ return function(){ alert('123'); } } var g = b(); g();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function d(callback){
    callback();
}
d(function(){
    alert('123')
});
 
//或者
 
function b(){
    return function(){
        alert('123');
    }
}
var g = b();
g();

其实第二种方式跟闭包的意义一样了,所谓的闭包书面的解释是可以访问另一个函数作用域内变量的函数,稍微改写一下可能会更明显。

JavaScript

function b(){ var name = '123'; return function(){ alert(name); } } var g = b(); g();

1
2
3
4
5
6
7
8
function b(){
    var name = '123';
    return function(){
        alert(name);
    }
}
var g = b();
g();

从这里可以看出来return的函数可以访问到name,而外部却不行,这个返回值的函数就可以理解为闭包。理解闭包还可以看一个经典的求值的例子。

JavaScript

function save_i(){ var a = []; for(var i = 0;i<10;i++){ a[i] = function(){ return i; } } return a; } var c = save_i(); for(var i = 0;i<10;i++){ alert(c[i]()); }

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

从这个例子上来看,我们想得到的结果是10次循环a[i]保存着一个闭包,然后alert出从0到10,但是结果很出人意料,全部是10,为什么?哪里理解的不对呢?a[i]明明是内部函数,然后让它访问另外一个函数作用域内的变量i。

个人觉得可以这样去分析问题,在客户端执行Js时有一个全局执行环境,指向的是window对象。而所谓的对象也就是引用类型,实际上在后台执行环境中,它就是一个指针。

回到Js当代码在执行的时候,会创建变量对象并且构建一个作用域链,而这个对象保存着当前函数可以访问的对象。

JavaScript

window ->save_i ->this|argument ->a ->i ->看不见的a[0]-a[10] ->a[0]function(){} ->i ->c

1
2
3
4
5
6
7
8
9
window
    ->save_i
        ->this|argument
        ->a
        ->i
        ->看不见的a[0]-a[10]
        ->a[0]function(){}
            ->i
    ->c

上述的i和a[0]里的i是同一个i,那么结果就是10。

进一步处理

JavaScript

function save_i(){ var a = []; for(var i = 0;i<10;i++){ a[i] = function(k){ return function(){ return k; }; }(i) } return a; } var c = save_i(); for(var i = 0;i<10;i++){ console.log(c[i]()); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function save_i(){
    var a = [];
    for(var i = 0;i<10;i++){
        a[i] = function(k){
            return function(){
                return k;
            };
        }(i)
    }
    return a;  
}
 
var c = save_i();
for(var i = 0;i<10;i++){
    console.log(c[i]());
}

接着按上面的节奏来分析

JavaScript

window ->save_i ->this|argument ->a ->i ->看不见的a[0]-a[10] ->a[0]function(){} ->k ->function(){} ->k ->c

1
2
3
4
5
6
7
8
9
10
11
12
window
    ->save_i
        ->this|argument
        ->a
        ->i
        ->看不见的a[0]-a[10]
        ->a[0]function(){}
            ->k
            ->function(){}
                ->k
 
    ->c

什么是传参?按值传递,相当于是在那个立即执行的函数中创建了一个新的地址和空间,虽然值是一样的,但是每一个k又是不同的,所以得到的结果正好满足了我们的预期。

本来正常情况下save_i执行完毕后就要销毁,但是内部的闭包被包含在这个作用域内了,所以save_i没法销毁,从这里可以看的出来闭包会带来内存的问题,因为用完之后没法销毁,如果不注意的话。

那么用完之后只能设置为null来解除引用,等着自动销毁把内存回收。

结束语

相信随着时代发展,clip-path会慢慢被浏览器厂商接受的。
本文有任何错误,欢迎评论留言。

打赏支持我写出更多好文章,谢谢!

打赏作者

Object

JavaScript的所有对象都衍生于Object对象,所有对象都继承了Object.prototype上的方法和属性,虽然它们可能会被覆盖,熟悉它对于编程能起到很大的作用,也能比较深刻的了解JavaScript这门语言。

Object

创建一个对象可以使用new,也可以使用快速创建的方式:

JavaScript

var _object = {};

1
var _object = {};

_object对象中就可以使用Object.prototype中所有的方法和属性,虽然看起来它是空的。说到这里在编程中常常有一个非常有用的需求,如何判断一个对象是空对象。

这是zepto中的判断一个对象是否是空对象,常常使用:

JavaScript

$.isEmptyObject = function(obj) { var name for (name in obj) return false return true }

1
2
3
4
5
$.isEmptyObject = function(obj) {
        var name
        for (name in obj) return false
        return true
}

也顺便看了下jQuery原理是一模一样的:

JavaScript

isEmptyObject: function( obj ) { var name; for ( name in obj ) { return false; } return true; }

1
2
3
4
5
6
7
isEmptyObject: function( obj ) {
    var name;
    for ( name in obj ) {
        return false;
    }
    return true;
}

使用in操作符来实现,它不会遍历到父原型链。

constructor返回一个指向创建了该对象的函数引用,这个东西主要是可以用来识别(类)到底是指向哪里的。

defineProperty直接在一个对象上定义一个新属性,非常适合用于动态构建,传入三个参数[动态添加对象的目标对象,需要定义或被修改的属性名,需要定义的对象],在第三个参数中可以有些属性来表示是否继承(proto),要不要定义get,set方法,enumerable是否可枚举。

defineProperties跟上述defineProperty一样,但是它可以添加多个。

getOwnPropertyNames返回一个由指定对象的所有属性组成的数组

keys返回一个数组包括对象所有的属性(可枚举)

keys是经常会用到的一个属性,它只能包可枚举的,如果想获取一个对象的所有属性包括不枚举的,那么使用getOwnPropertyNames。

hasOwnProperty用于判断某个对象是否包含有自身的属性,这个方法常常用于检测对象中的属性是否存在,它只检测自身,对于继承过来的都是false,这一点是非常重要的理解。

isPrototypeOf 用于检测一个对象是否在另一个对象的原型链上,比如有两个对象是互相交互的,常常会使用它来进行检测。

propertyIsEnumerable这个方法也比较重要,返回一个布尔值,检测一个对象的自身属性是否可以枚举

可枚举的理解,也就是对象的属性可枚举,它的属性值不可以修改,但是在Js中它有自己的定义,引擎内部看不见的该属性的[[Enumerable]]特性为true,那么就是可枚举的。基本上把一个普通对象可以看做是一个枚举类型,比如var color = {‘red’:1},red是可以修改的,但是red是可枚举的,但是如果是继承过来的属性,propertyIsEnumerable是返回false的,它还有一个特点,就是自身。

如果要定义不可枚举的属性,那就要使用defineProperty方法了,目前不能用对象直接量或者构造函数定义出来。

JavaScript

var obj = {name: 'jack', age:23} Object.defineProperty(obj, 'id', {value : '123', enumerable : false });

1
2
var obj = {name: 'jack', age:23}
Object.defineProperty(obj, 'id', {value : '123', enumerable : false });

打赏支持我写出更多好文章,谢谢!

任选一种支付方式

澳门新葡亰手机版 17 澳门新葡亰手机版 18

1 赞 2 收藏 评论

深拷贝与浅拷贝

关于拷贝的问题,主要分为深拷贝和浅拷贝,但是如果从空间分配上来说JavaScript的拷贝不应该算是深拷贝,比如:

JavaScript

var d = {}; for(k in a){ d[k] = a[k]; } return d;

1
2
3
4
5
var d = {};
for(k in a){
    d[k] = a[k];
}
return d;

今天突然想到了这么一个问题,在C语言中,所谓的拷贝,就是分两种情况,一种是把指针地址拷贝给另外一个变量,虽然也开辟的了一个内存空间,在栈上也存在着一个地址,我对这个变量进行修改,同一个指针是会改变其值的,这种拷贝叫浅拷贝。另外一种情况,直接开辟一个新空间,把需要复制的值都复制在这个新的空间中,这种拷贝叫中深拷贝。

如果看到上述的一段Js代码,很多人说它是浅拷贝,假设传入一个a对象,拷贝完成之后返回一个d,当我修改返回对象的值时并不能同时修改a对象,于是,在这里我有一个很大的疑问,在Js中到底什么是浅拷贝,什么是深拷贝的问题?

这一点上感觉Js真的很奇葩,如果在开发iOS中,不可变对象copy一下,依然是不可变,所以是浅拷贝,拷贝了指针变量中存储的地址值。如果是可变对象copy一下,到不可变,空间变化了,包括不可变mutableCopy到不可变,空间依然变化了,所以是深拷贝。但是JavaScript中对于这一点要考虑一种情况,值类型,和引用类型,这个基础知识,我相信大家都非常清楚。数字,字符串等都是值类型,object,array等都是引用类型。

JavaScript

var a = [1,2,3]; var b = a; b.push(4); console.log(a); //[1,2,3,4] var numb = 123; var _numb = numb; _numb = 567; console.log(numb); //123

1
2
3
4
5
6
7
8
9
10
11
var a = [1,2,3];
var b = a;
 
b.push(4);
console.log(a); //[1,2,3,4]
 
var numb = 123;
var _numb = numb;
_numb = 567;
 
console.log(numb); //123

从这个例子中可以看的出来,它们使用的都是=符号,而数组a发生了变化,numb数字却没有发生变化。那么从这里,可以有一个总结,所谓了深拷贝,浅拷贝的问题,应该针对的是有多个嵌套发生的情况。不然假设是这样的情况,还能叫浅拷贝么?

JavaScript

var object = {"de":123}; var o = copy(object); o.de = 456; console.log(object) //{"de":123}

1
2
3
4
var object = {"de":123};
var o = copy(object);  
o.de = 456;
console.log(object) //{"de":123}

明显对象o中的de属性修改并没有影响到原始对象,一个对象中的属性是一个字符串,如果从内存空间的角度上来说,这里明显是开辟了新的空间,还能说是浅拷贝么?那么针对另外一种情况。

JavaScript

var object = { "de":{ "d":123 } } var o = deepCopy(object); o.de.d = "asd";

1
2
3
4
5
6
7
var object = {
    "de":{
        "d":123
    }
}
var o = deepCopy(object);
o.de.d = "asd";

如果一个对象中的第一层属性,不是值类型,只单层循环,这样来看的话确实是一个浅拷贝,因为在Js中引用类型用=赋值,实际上是引用,这样说的通。所以,深拷贝,还需要做一些处理,把object,array等引用类型识别出来,深层递归到最后一层,一个一个的拷贝。

JavaScript

var deepCopy = function(o){ var target = {}; if(typeof o !== 'object' && !Array.isArray(o)){ return o; } for(var k in o){ target[k] = deepCopy(o[k]); } return target; }

1
2
3
4
5
6
7
8
9
10
var deepCopy = function(o){
    var target = {};
    if(typeof o !== 'object' && !Array.isArray(o)){
        return o;
    }
    for(var k in o){
        target[k] = deepCopy(o[k]);
    }
    return target;
}

思路是如此,这个例子只考虑了两种情况,对象和数组,为了验证这样的思路,最后的结果与预期是一样的。

JavaScript

var _copy = { 'object':{ 'name':'wen' }, 'array':[1,2] } var h = deepCopy(_copy); h.object.name = 'lcepy'; h.array[1] = 8; console.log(h); console.log(_copy);

1
2
3
4
5
6
7
8
9
10
11
var _copy = {
    'object':{
        'name':'wen'
    },
    'array':[1,2]
}
var h = deepCopy(_copy);
h.object.name = 'lcepy';
h.array[1] = 8;
console.log(h);
console.log(_copy);

关于作者:Damonare

澳门新葡亰手机版 19

知乎专栏[前端进击者] 个人主页 · 我的文章 · 19 ·          

澳门新葡亰手机版 20

面向对象

面向对象的语言有一个非常明显的标志:类,通过类来创建任意多个具有相同属性和方法的对象,可惜的是Js里没有这样的概念。

但是Js有一个特性:一切皆是对象。

聪明的开发者通过这些特性进行摸索,于是迂回发明了一些程序设计,以便更好的组织代码结构。

工厂模式

主要是用来解决有多个相同属性和方法的对象的问题,可以用函数来封装特定的接口来实现

JavaScript

var computer = function(name,version){ return { 'name':name, 'version':version, 'showMessage':function(){ alert(this.name); } } } var test = computer('apple','11.1'); test.showMessage();

1
2
3
4
5
6
7
8
9
10
11
var computer = function(name,version){
    return {
        'name':name,
        'version':version,
        'showMessage':function(){
            alert(this.name);
        }
    }
}
var test = computer('apple','11.1');
test.showMessage();

构造函数模式

我们知道像原生的构造函数,比如Object,Array等,它们是在运行时自动出现在执行环境中的。因此,为了模仿它,这里也可以通过一个普通的函数,并且new出一个对象,这样就成为了自定义的构造函数,也可以为他们添加自定义的属性和方法。

但是这样的构造函数有一个缺陷,就是每个方法都会在每个实例上创建一次,因为每次创建都需要分配内存空间,但是有时候这样的特性还是有用的,主要是要控制它们,在不使用的时候释放内存。

JavaScript

var Computer = function(name,version){ this.name = name; this.version = version; this.showMessage = function(){ alert(this.name); } } var apple = new Computer('apple',2014); var dell = new Computer('dell',2010); apple.showMessage(); dell.showMessage();

1
2
3
4
5
6
7
8
9
10
11
var Computer = function(name,version){
    this.name = name;
    this.version = version;
    this.showMessage = function(){
        alert(this.name);
    }
}
var apple = new Computer('apple',2014);
var dell = new Computer('dell',2010);
apple.showMessage();
dell.showMessage();

像apple,dell是通过Computer实例化出来的不同的对象,但是它们的constructor都是指向Computer的。这里也可以使用instanceof来对(对象)进行检测。

在书写上构造函数跟其他函数是没有什么区别的,主要的区别还是在使用上,构造函数需要使用new操作符。

其实这样的书写,已经跟类没有什么区别了,表面上来看,而构造函数我个人更倾向于一个类的某个静态方法。

原型模式

说到原型模式就不得不提一提关于指针的问题,因为每一个函数都有一个prototype属性,而这个属性是一个指针,指向一个对象。

C语言描述指针,这个在iOS开发中非常重要

比如我先定义一个int类型的指针变量和一个普通的int类型数据,然后给指针变量赋值。

JavaScript

int *p; int pp = 123; p = &pp; *p = 999; printf('%d',pp);

1
2
3
4
5
    int *p;
    int pp = 123;
    p = &pp;
    *p = 999;
    printf('%d',pp);

*是一个特殊符号用于标明它是一个指针变量。

&是地址符

分析这个就要说到栈内存和堆内存了,比如*p在栈内存中分配了一个地址假设是ff22x0,它还没有空间。而pp存在一个地址ff23x0,并且分配了一个空间存储着123,这个地址是指向这个空间的。

p = &pp 这样的赋值操作,也就是把ff23x0取出来,并且给p分配一个空间把ff23x0存储进去,并且ff22x0指向这个空间。

*p = 999 从这里就可以看出来p操作的是地址,而这个地址不就是ff23x0么,于是pp成了999。

所谓的指针也就是存储着地址的变量。

回到原型上,如果每一个函数中的

prototype属性都是一个指针,实际上它只是一个地址引用着一个空间,而这个空间正是我们写的xxx.prototype.xxx

function(){}这样的代码在运行时分配的空间。那么可见,使用原型的好处是空间只分配一次,大家都是共享的,因为它是指针。

先看一个例子

JavaScript

var Computer = function(name){ this.name = name; } Computer.prototype.showMessage = function(name){ alert(name); } var apple = new Computer('apple'); var dell = new Computer('dell'); Computer.prototype.isPrototypeOf(apple);

1
2
3
4
5
6
7
8
9
10
var Computer = function(name){
    this.name = name;
}
Computer.prototype.showMessage = function(name){
    alert(name);
}
 
var apple = new Computer('apple');
var dell = new Computer('dell');
Computer.prototype.isPrototypeOf(apple);

在解释这个原型链之前,还要明白Js的一个特性,就是如果自身不存在,它会沿着原型往上查找。它的原理稍微有些绕,Computer自身的prototype是指向它自身的原型对象的,而每一个函数又有一个constructor指向它自身,prototype.constructor又指向它自身。于是Computer的两个实例apple,dell内部有一个proto属性是指向Computer.prototype的,最后的结果是它们可以使用showMessage方法。

当然它们还有一个搜索原则,比如在调用showMessage的时候,引擎先问apple自身有showMessage吗?“没有”,继续搜索,apple的原型有吗,“有”,调用。所以从这里可以看出,this.showMessage是会覆盖prototype.showMessage的。

另外还可以使用isPrototypeOf来检测一个对象是否在另一个对象的原型链上,上述的代码返回的是true。

JavaScript

apple.hasOwnProperty('name') apple.hasOwnProperty('showMessage')

1
2
apple.hasOwnProperty('name')
apple.hasOwnProperty('showMessage')

使用hasOwnProperty来检测到底是对象属性还是原型属性,使用this创建的属性是一个对象属性。

从上面可以看出来原型链的好处,但是它也不是万能的,正因为指针的存在,对于某些引用类型来说这个就非常不好了,我需要保持原对象属性值是每一个对象特有的,而不是共享的,于是把之前的构造函数与原型组合起来,也就解决了这样的问题。

JavaScript

var Computer = function(name){ this.name = name; } Computer.prototype.showMessage = function(){ alert(this.name); } var apple = new Computer('apple'); apple.showMessage();

1
2
3
4
5
6
7
8
var Computer = function(name){
    this.name = name;
}
Computer.prototype.showMessage = function(){
    alert(this.name);
}
var apple = new Computer('apple');
apple.showMessage();

这样的结果是在对象中都会创建一份属于自己的属性,而方法则是共享的。

动态原型模式

有时候遇到某些问题需要动态添加原型,但是实例中是不能添加的,所以绕来一下,在初始化构造函数中添加。

JavaScript

var Computer = function(){ if(typeof this.showMessage !== 'function'){ Computer.prototype.showMessage = function(){ } } }

1
2
3
4
5
6
7
var Computer = function(){
    if(typeof this.showMessage !== 'function'){
        Computer.prototype.showMessage = function(){
 
        }
    }
}

只要初始化了一次,以后就不用修改了。

寄生构造函数模式

这种模式的原理就是在一个函数中封装需要创建对象的代码,然后返回它。

JavaScript

var test = function(name){ return { 'name':name } } var g = new test('apple'); var f = de('dell');

1
2
3
4
5
6
7
var test = function(name){
    return {
        'name':name
    }
}
var g = new test('apple');
var f = de('dell');

看起来它跟工厂模式还是很像的,

稳妥模式

这种模式主要是在解决需要安全的环境中使用,一般来说一个类如果不提供getter,setter方法,是不允许直接访问和修改的。

JavaScript

var computer = function(name){ var _name = name; return { 'getter':function(){ return _name; }, 'setter':function(name){ _name = name; } } }

1
2
3
4
5
6
7
8
9
10
11
var computer = function(name){
    var _name = name;
    return {
        'getter':function(){
            return _name;
        },
        'setter':function(name){
            _name = name;
        }
    }
}

这样的方式可以保证属性或者说是数据的安全性,不允许直接随便修改,如果不提供setter方法的话,压根就不允许。

本文由澳门新葡亰手机版发布于web前端,转载请注明出处:css属性的选择对动画性能的影响,前端开发基础

上一篇:这可能是史上最全的CSS自适应布局总结,移动前 下一篇:元素的几种方法的对比,层叠上下文
猜你喜欢
热门排行
精彩图文