旋转遮罩
前言
我们加载资源等待时,需要将指定区域覆盖,使其失去原有的功能(如: 点击事件,移入移出效果)。在指定区域加上一个半透明的蒙板,也可以再在蒙版上面再加一个你想要实现的效果。一般通过定位(position) ,使用层级z-index
来实现遮罩层的效果。
知识基石
+
作为二元操作符放操作对象之后
,其中一个操作数为字符串类型时,另一个操作数将会被无条件转为字符串类型:
//值类型
var foo = 3 + ''; // "3"
var foo = true + ''; // "true"
var foo = undefined + ''; // "undefined"
var foo = null + ''; // "null"
// 引用类型
var foo = [1, 2, 3] + ''; // "1,2,3"
var foo = {} + ''; // "[object Object]"
作为一元操作符操作对象之前
,将操作数转换为数字类型,如果转型失败,则会返回NaN
:
//值类型
var foo = +''; // 0
var foo = +'3'; // 3
var foo = +'3px'; // NaN
var foo = +false; // 0
var foo = +true; // 1
var foo = +null; // 0
var foo = +undefined; // NaN
//引用类型
var foo = Object("12")
console.log(+foo)// 12
?和!
读ECMA规范时长会有Let key be ? ToPrimitive(argument, string)
或者Return ! ToString(key).
函数前带?
或!
。要理解它们,需要先理解完成记录(Completion Record),完成记录是一种规范类型(只在规范中使用)。JavaScript 引擎不需要实现对应的内部数据类型。完成记录是一种记录类型(Record),而记录具有一组固定的命名字段。
所有抽象操作都会隐式返回一个完成记录。即便一个抽象操作看起来返回简单类型(如 Boolean)的值,这个值也会被隐式包装在一个normal
类型(正常完成)的完成记录中返回。规范本身在这方面也不是完全一致。有一些辅助函数会返回裸值,而这些值将直接被使用,无需从完成记录中提取。不过这种情况在上下文中通常能够一目了然。如果某个算法抛出异常,则意味着返回的完成记录的[[Type]]
为throw
,[[Value]]
为异常对象。我们这里不讨论break、continue和return类型(规范中没有相应的例子,因为这几种类型不能跨函数)。
对于完成记录,如果是硬性完成,则立即返回;如果是正常完成,则提取完成记录的值。
ReturnIfAbrupt
看起来虽然像函数调用,但它不是。ReturnIfAbrup会导致它所在位置的函数返回,而不是ReturnIfAbrupt本身返回。
1.令obj为Foo();(obj是一个完成记录。)
2.ReturnIfAbrupt(obj);
3.Bar(obj)。(如果到了这一步,obj已经变成了从完成记录中提取出来的值。)
所以? Foo()等价于ReturnIfAbrupt(Foo())
同理
- let val为! Foo()
- 断言:val非硬性完成
- 设val为val.[[Value]]。
叹号表示从正常完成记录中提取值。
[ ]和.
根据ECMA标准它们都是属性访问器,也同属于Left-Hand-Side Expressions
。
.点号
MemberExpression . IdentifierName
CallExpression . IdentifierName
[ ]括号
MemberExpression [ Expression ]
CallExpression [ Expression ]
共通点
MemberExpression.标识符名称≈MemberExpression [ <标识符名称字符串>]
CallExpression.标识符名称≈CallExpression [<标识符名称字符串>]
区别体现在对key值的计算
中括号 EvaluatePropertyAccessWithExpressionKey ( baseValue, expression, strict )
- Let propertyNameReference be the result of evaluating expression.
- Let propertyNameValue be ? GetValue(propertyNameReference).
- Let propertyKey be ? ToPropertyKey(propertyNameValue).
- Return the Reference Record { [[Base]]: baseValue, [[ReferencedName]]: propertyKey, [[Strict]]: strict, [[ThisValue]]: empty }.
点号 EvaluatePropertyAccessWithIdentifierKey ( baseValue, identifierName, strict )
- Let propertyNameString be StringValue of identifierName.
- Return the Reference Record { [[Base]]: baseValue, [[ReferencedName]]: propertyNameString, [[Strict]]: strict, [[ThisValue]]: empty }.
样式
style
和class
同时对标签的同一个属性进行个性化时,style中的属性设置优先使用。class
中多个,最后一个覆盖前面的。设置css
(<style>标签中定义)可以带-
,在js
中以驼峰替换-
。
css样式选择器
- 类选择器
.class
选择器为所有具有指定类的元素添加样式。.
来选择具有包含特定值的类的元素。
//首先应该是段落<p>标签,赋予类属性class=urgent,才能起作用
p.urgent {color: black;}
- #id 选择器
#id
选择器指定具有id的元素的样式。element1#idname
,如果没有元素名称在#
之前,则选择器匹配包含该ID值的所有元素。
//<h1>标签具有id='page-title',字体才会放大2.5倍
h1#page-title {font-size: 250%;}
//<body>标签具有id='home' 背景颜色才是银色
body#home {background: silver;}
//只要具有id='firstname'的元素都会显示背景色黄色
#firstname {background-color:yellow;}
- *选择器
选择器匹配文档中的每个元素,包括html和body元素。可用于为另一个元素内的所有元素添加样式。
//引用该属性所在css样式的html中所有元素都是粉色背景
*{background-color:peach;}
//div下的所有元素背景紫色
div *{background-color:purple;}
<div class="intro">
<p id="firstname">My name is Donald.</p>
<p id="hometown">I live in Duckburg.</p>
</div>
- element 选择器
元素选择器,样式添加到具有指定元素名称的所有元素。
//p标签字体大小1em
p {font-size: 1em;}
数据类型
值类型:字符串String
、数字 Number
、布尔 Boolean
、空Null
、未定义Undefined
、Symbol
。
引用数据类型:对象 Object
、数组 Array
、函数 Function
。还有两个特殊的对象:正则RegExp
和日期Date
。
Symble
对象属性名都是字符串,这容易造成属性名的冲突, ES6引入Symbol的原因。每一次创建它就相当于生成UUID
,唯一的。symbol 是一种基本数据类型 (primitive data type)。Symbol()函数会返回symbol类型的值,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的symbol注册。
获取
Symbol.for()
方法和Symbol.keyFor()
方法从全局的symbol注册表设置和取得symbol。for ... in
找不到Symbol
,要通过Object.getOwnPropertySymbols()
查找一个给定对象的符号属性时返回一个symbol类型的数组。
Symbol.toPrimitive
Symbol.toPrimitive
是一个内置的Symbol
值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。在 Symbol.toPrimitive
属性(用作函数值)的帮助下,一个对象可被转换为原始值。该函数被调用时,会被传递一个字符串参数 hint ,表示要转换到的原始值的预期类型。 hint 参数的取值是 “number”、“string” 和 “default” 中的任意一个。该方法在转基本类型时调用优先级最高。
var obj2 = {
[Symbol.toPrimitive](hint) {
if (hint == "number") {
return 10;
}
if (hint == "string") {
return "string";
}
return true;
}
};
对象属性定义
对象的只能是String
和Symbol
,但String
类型的key可以重复而 Symbol
类型的key是唯一的。Symbol
的本质是表示一个唯一标识。无论怎么创建对象,底层定义属性逻辑不变的Object.defineProperty(obj, prop, descriptor)
//要定义属性的对象。
obj
//要定义或修改的属性的名称或Symbol。
prop
//要定义或修改的属性描述符。
descriptor
- 如果要定义属性的是对象,否则直接抛出错误
ToPropertyKey
处理要定义或修改的属性的名称或Symbol.ToPrimitive
获取处理要定义或修改的属性的名称或Symbol的原始值。
Object.defineProperty(obj, prop, descriptor)
- If Type(O) is not Object, throw a TypeError exception.
- Let key be ? ToPropertyKey(P).
- Let desc be ? ToPropertyDescriptor(Attributes).
- Perform ? DefinePropertyOrThrow(O, key, desc).
- Return O.
ToPropertyKey(p)
- Let key be ? ToPrimitive(argument, string).
- If Type(key) is Symbol, then
a. Return key.- Return ! ToString(key).//返回key的ToString
Return ! ToString(key)
这是整个对象产生属性名的最后一步,所有的对象属性定义之后都会转化成对应原始值的的字符串。
ToPrimitive ( input [ , preferredType ] )
1.If Type(input) is Object, then //输入的是对象
// GetMethod获取func:input原始值的方法(xxx.toPrimitive),注:@@对象来自何处
a. Let exoticToPrim be ? GetMethod(input, @@toPrimitive).
//存在toPrimitive方法
b. If exoticToPrim is not undefined, then如果传入了期望类型,但此类型不存在,用默认defualt
i. If preferredType is not present, let hint be “default”.
//如果传入的是string,则设置转换为string
ii. Else if preferredType is string, let hint be “string”.
iii. Else,
//否则设置为数字类型
- Assert: preferredType is number.
- Let hint be “number”.
//回调输入的input对象的toPrimitive方法获取原始值
iv. Let result be ? Call(exoticToPrim, input, « hint »).
//结果是值类型包括Symble,返回这个原始值,否则抛出错误
v. If Type(result) is not Object, return result.
vi. Throw a TypeError exception.//input对象不存在toPrimitive方法,期望类型又不存在,就设置number
c. If preferredType is not present, let preferredType be number.
//普通获取input的数字原始值
d. Return ? OrdinaryToPrimitive(input, preferredType).2.Return input.##如果输入的是值类型,直接返回
简单说preferredType
是number
:先input.valueof()
,结果是原始值,返回结果;否则input.toString()
,结果是原始值,返回结果。否则抛出错误。当preferredType是string
,这两个方法交换顺序。省略preferredType
,日期会转字符串,其他当作number
。
对象属性名称只能是String
、Symble
。String
可以用.
也可以用[]
获取属性值。值类型中其他类型和引用类型要作为对象属性名必须用[]
,也只能通过[]
来获取。前面提到的ToPrimitive(argument, string).
所有属性名最后都会转化成String
。
var va = 'name';//值类型(String)
var b = true; //值类型(boolean)
var o = {xin:'lei'}; //对象(引用类型)
var o1 = {kaixing:'haha'};//对面象(引用类型)
var array = [1,2,3];//数组(引用类型)
var array1 = [4,5,6];//数组(引用类型)
var sy = Symbol('uuid');//Symbol
var obj ={
va:'我不是变量,我只是属性名,而且是默认带引号的字符串',
[va]:'变量作为属性名必须带[]',
[b]:'布尔',
[o]:'对象',
[o1]:'对象2',
[array]:'数组',
[array1]:'数组2',
[sy]:'Sym'
}
o1.toString = function(){return '{'+'kaixing:'+this.kaixing+'}'}
重写了toString
方法,对象作为属性名就不会被覆盖了。
void 0&undefined
void
接受一个参数,无论这个参数是什么,返回值永远是undefined
。void 0
少三个字节,用来代替undefined节省空间,这个意义不大牺牲了可读性。undefined
不是js中保留字,我们可以使用undefined作为变量名字。
function void_undefined(){
alert(undefined); //alerts "undefined"
var undefined = "new value";
alert('var undefined:'+undefined);
//void无论什么参数都返回undefined
alert('void 0:'+ void 0)
alert('void (0):'+void (0))
alert('void "hello:"'+void "hello")
alert('void "hello":'+void (new Date()))
}
hasOwnProperty
JavaScript中对象原型上的hasOwnProperty()
用来判断一个属性是定义在对象本身而不是继承自原型链。obj.hasOwnProperty(prop)
,hasOwnProperty不是保留字。一旦它被定义为对象的属性名,再用上面的方法就不凑效了,所以判断一个属性是不是对象自身的属性,保险的方法就是Object.prototype.hasOwnProperty.call(fo, 'pro');
空对象
/**{}就开辟了一个新的内催地址。var obj= {name:a},obj={name:b},obj就改变了内存指向。如果修改属性,不改变内存指向obj.name = b,才是修改属性值
*/
var obj = {}
//这种方法必须声明var,否则报target未定义
var target = Object(target);
对象拷贝
assign
Object.assign()
用于将所有可枚举的自有属性的值从一个或多个源对象复制到目标对象,它将返回目标对象。Object.assign(target, …sources)
var obj1 = {
name:'UK',
country:{c1:'England',c2:'Scotland',c3:'Wales',c4:'Ireland'}
};
var obj2 ={
tag:'Husky'
};
var obj = Object.assign({}, obj1, obj2);
console.log(obj);
obj1.country.c4 = 'NULL'
由此可见assign是浅拷贝。IE不支持这个方法。那我们就得自己写了。
if (typeof Object.assign != 'function') {//浏览器不支持Object.assign
/**
* 构建assign函数
*/
Object.assign = function(target,...sources) {
if (target == null) {
//需要合并到的目标对象为null,抛出错误
throw new TypeError('Cannot convert undefined or null to object');
}
//确保target是一个对象,才能给他增加key属性
target = Object(target);
for (var index = 1; index < arguments.length; index++) {
//第一个参数是目标对象,所以从第二个参数开始才是新增拷贝属性的源对象
var source = arguments[index];
if (source != null) {
for (var key in source) {//遍历目标对象的属性
if (Object.prototype.hasOwnProperty.call(source, key)) {
//该属性是定义在对象本身而不是继承自原型链,就拷贝属性到目标对象
target[key] = source[key];
}
}
}
}
//返回目标对象
return target;
};
}
深拷贝
assign
是浅拷贝,那我们怎么进行深拷贝?浅拷贝,无非就是对象拷贝完指向地址不变,所以一个改变另一个就跟着变了。java
深拷贝除了重写clone方法和反射外,还有就是我们JDBC
操作时循环内new对象,每一次都附上一条新属性。根据这个思路,只要我们遇到对象就深究,知道只剩值类型属性。这样拷贝下来就不存在引用不变问题了。同时注意Symbol
属性的拷贝。
Object.deep_assign = function(target,...sources) {
if (target == null) {
//需要合并到的目标对象为null,抛出错误
throw new TypeError('Cannot convert undefined or null to object');
}
//使用递归实现深拷贝
function deepClone(obj) {
//判断拷贝的obj是对象还是数组,并创建一个空对象
var objClone = Array.isArray(obj) ? [] : {};
for (key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
if (obj[key] && typeof obj[key] === "object") { //obj里面属性值不为空并且还是对象,进行深度拷贝
objClone[key] = deepClone(obj[key]); //递归进行深度的拷贝
} else{
objClone[key] = obj[key]; //直接拷贝
}
}
}
//获取对象的Symbol属性,它是无法通过key in取到
var symb = Object.getOwnPropertySymbols(obj)
if(symb){//存在symbol属性
for(var i = 0;i < symb.length; i++){
objClone[symb[i]] = obj[symb[i]];
}
}
return objClone;
}
//确保target是一个对象,才能给他增加key属性
target = Object(target);
for (var index = 1; index < arguments.length; index++) {
//第一个参数是目标对象,所以从第二个参数开始才是新增拷贝属性的源对象
var source = arguments[index];
if (source != null) {
for (var key in source) {//遍历目标对象的属性
if (Object.prototype.hasOwnProperty.call(source, key)) {
//该属性是定义在对象本身而不是继承自原型链,就拷贝属性到目标对象
var obj = source[key];
if (obj && typeof obj === "object") {
target[key] = deepClone(obj);
}else{
target[key] = source[key];
}
}
}
//获取对象的Symbol属性,它是无法通过key in取到
var symb = Object.getOwnPropertySymbols(source)
if(symb){//存在symbol属性
for(var i = 0;i < symb.length; i++){
target[symb[i]] = source[symb[i]];
}
}
}
}
//返回目标对象
return target;
};
var inner_obj = {
name:'内部对象',
[Symbol('uuid1')]:768
}
var source = {
sour:'源',
time:'2022',
io:inner_obj,
[Symbol('uuid2')]:'卍'
}
var target ={
tg:'接收'
}
spin
加载等待离不开spin的友好页面。现在我们分析一下它的源码(v4.1.1)。
__assign
为了适配IE浏览器,创建一个_assign
方法,它来合并new Spinner(opt)
中传入的opt
旋转图形样式和默认的defaults
样式属性。
/**__assign方法已经创建过,不需要创建了,否则再看浏览器是否支持assign,支持则直接使
* 用Object.assign,这些都不满足创建新的函数__assign
*/
var __assign = (this && this.__assign) || Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
//第一个参数是目标对象,所以从第二开始才是需要合并的源的对象
s = arguments[i];
/**确保该属性是定义在对象本身而不是继承自原型链,就拷贝属性到目标对象
* 同名属性,后者覆盖前者
*/
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
//返回合并后的样式属性
return t;
};
默认属性
var defaults = {
lines: 12,//花瓣数目
length: 7,// 花瓣长度
width: 5,// 花瓣宽度
radius: 10,// 花瓣距中心半径
scale: 1.0,//spin的整体缩放
corners: 1,//花瓣四角圆滑度 (0-1)
color: '#000',//花瓣颜色
fadeColor: 'transparent',//花瓣渐变色["red","green","yellow"]会出现红黄绿
opacity: 0.25,/**不透明度。背景色的默认值:transparent意思才是“透明的”0.0就和
transparent一样了,看不到但是实实在在存在。*/
rotate: 0,//从x轴顺时针第一个花瓣与x轴夹角。花瓣旋转角度
direction: 1,//花瓣旋转方向 1: 顺时针, -1: 逆时针
speed: 1,//花瓣旋转速度
trail: 100,//花瓣旋转时的拖影(百分比)
//动画频帧,最新版本已经取消了,为了支持IE9。css有了animation就不再手动写动画
//fps: 20,
zIndex: 2e9,//spinner的z轴 (默认是2000000000),层叠顺序越大越顶层,越靠近用户
className: 'spinner',//spinner css样式名,花瓣div的父级div class名
top: '50%',//spinner相对父容器Top定位,不是花瓣div,花瓣div是spinner的一个个子div
left: '50%',//spinner相对父容器left定位。这两个控制className位置的。
shadow: 'none',//花瓣是否显示阴影
position: 'absolute',//spinner的position
hwaccel: false, // 不使用硬件加速
};