js中Object常用方法和属性
Object的常用方法和属性也是很常用的。
一、属性
Object自带一个prototype的属性,即Object.prototype, Object.prototype本身也只是一个对象,也会有一些属性和方法。
1.属性
Object.prototype.writable:默认为false
Object.prototype.enumerable:默认为false
Object.prototype.configurable:默认为false
Object.prototype.constructor:用于创建一个对象的原型。
2、常用方法
Object.prototype.hasOwnProperty():返回一个布尔值,表示某个对象是否含有指定的属性,而且此属性非原型链继承。
Object.prototype.isPrototypeOf():返回一个布尔值,表示指定的对象是否在本对象的原型链中。
Object.prototype.propertyIsEnumerable():判断指定属性是否可枚举。
Object.prototype.toString():返回对象的字符串表示。
Object.prototype.watch():给对象的某个属性增加监听。
Object.prototype.unwatch():移除对象某个属性的监听。
Object.prototype.valueOf():返回指定对象的原始值。
二、方法
Object.assign(target,...sources):把任意多的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。
Object.create(proto,[propertiesobject]):创建一个拥有指定原型和若干个指定属性的对象。
Object.defineProperty(obj,prop,descriptor):直接在一个对象上定义一个新属性,或者修改一个已经存在的属性,并返回这个对象。obj:需要定义属性的对象。prop:需定义或修改的属性的名字。descriptor:将被定义或修改的属性的描述符。
Object.entries(obj):返回一个包含由给定对象所有可枚举属性的属性名和属性值组成的 [属性名,属性值] 键值对的数组,数组中键值对的排列顺序和使用for…in循环遍历该对象时返回的顺序一致。
举例:
var obj = { foo: “bar”, baz: 42 };
console.log(Object.entries(obj)); // [ [‘foo’, ‘bar’], [‘baz’, 42] ]
Object.freeze(obj):冻结一个对象,冻结指的是不能向这个对象添加新的属性,不能修改其已有属性的值,不能删除已有属性,以及不能修改该对象已有属性的可枚举性、可配置性、可写性。也就是说,这个对象永远是不可变的。该方法返回被冻结的对象。
Object.getOwnPropertyDescriptor(obj, prop):返回指定对象上一个自有属性对应的属性描述符。
Object.getOwnPropertyNames(obj):返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性)组成的数组。
举例:
// 类数组对象
var obj = { 0: “a”, 1: “b”, 2: “c”};
console.log(Object.getOwnPropertyNames(obj).sort()); // [“0”, “1”, “2”]
Object.getPrototypeOf(object):返回该对象的原型。
Object.is(value1, value2):判断两个值是否是同一个值。
Object.isExtensible(obj):判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。
Object.isFrozen(obj):判断一个对象是否被冻结(frozen)。
Object.isSealed(obj):判断一个对象是否是密封的(sealed)。密封对象是指那些不可 扩展 的,且所有自身属性都不可配置的(non-configurable)且属性不可删除的对象(其可以是可写的)。
Object.keys(obj):返回一个由给定对象的所有可枚举自身属性的属性名组成的数组,数组中属性名的排列顺序和使用for-in循环遍历该对象时返回的顺序一致
举例:
var arr = [“a”, “b”, “c”];
alert(Object.keys(arr)); // 弹出”0,1,2”
// 类数组对象
var obj = { 0 : “a”, 1 : “b”, 2 : “c”};
alert(Object.keys(obj)); // 弹出”0,1,2”
Object.preventExtensions(obj):让一个对象变的不可扩展,也就是永远不能再添加新的属性。
Object.setPrototypeOf(obj, prototype):将一个指定的对象的原型设置为另一个对象或者null
Object.values(obj):返回一个包含指定对象所有的可枚举属性值的数组,数组中的值顺序和使用for…in循环遍历的顺序一样。
举例:
var obj = { foo: “bar”, baz: 42 };
console.log(Object.values(obj)); // [‘bar’, 42]
Object.assign()与深拷贝
深拷贝与浅拷贝
所谓深拷贝与浅拷贝,是围绕引用类型变量的拷贝进行的讨论。
在ecmascript中,变量分为基本类型和引用类型两种。其本质区别是不可变性,基本类型是不可变的,而引用类型是可变的。
所谓基本类型的不可变性,我们可以举个例子
let a = 1;
let b = 1;
a++;
a===2;//true
b===1;//true
声明一个变量a,并为其赋值1,这时内存中开辟出一片区域用来储存1。此时声明了一个变量b,也为b赋值1。当执行a++时,
基本类型的不可变性就体现出来,a++的值应该为2,但是这个值并不会将原来储存1的那片内存覆盖掉,而是再开辟一片内
存来存储2。所以对于这个1来讲,他是永远不可变的。
而对于引用变量则不同,因为其存储的是只想某个内存区域的地址,所以其修改时直接操作在内存上的,
这就导致了深拷贝和浅拷贝问题的出现。
浅拷贝
let foo = {
x: 1,
y: -1
}
let bar = foo;
foo.x++;
foo.x ===2 //true
bar.x ===2 //true
这就是最简单的浅拷贝,其效果十分明显,对拷贝源的操作,会直接体现在拷贝目标上,因为这个赋值行为的本质是内存地址的赋值,
所以他们指向了同一片内存区域。
浅拷贝十分容易,也十分常见,但却无法满足需求,加入我们需要获得拷贝源完全相同,却又不会互相影响的对象,应该怎么办呢
Object.assign()
ES6为我们提供了一种十分好用的方法,Object.assign(target, ...source)方法
assign方法接受多个参数,第一个参数target为拷贝目标,剩余参数...source是拷贝源。此方法可以将...source中的属性复制到target中,
同名属性会进行覆盖,并且在复制过程中实现了'伪'深拷贝
let foo = {
a: 1,
b: 2,
c: {
d: 1
}
}
let bar = { };
Object.assign(bar, foo);
foo.a++;
foo.a === 2 //true
bar.a === 1 //true
乍一看,好像已经实现了深拷贝的效果,对foo.a进行的操作并没有体现在bar.a中,但是再往后看
foo.c.d++;
foo.c.d === 2 //true
bar.c.d === 1 //false
bar.c.d === 2 //true
Object.assign()的拷贝类型十分明显了,这是一种可以对非嵌套对象进行深拷贝的方法,如果对象中出现嵌套情况,那么其对被嵌套对象的行为就成了普通的浅拷贝.
如果真的想进行深拷贝,最简单粗暴地方式就是JSON操作。
JSON对象中包含两个方法,stringify( )和parse( ),前者可以将对象JSON化,而后者可以将JSON格式转换为对象,这是一种可以实现深拷贝的方法。
但这种方法的缺陷是会破坏原型链,并且无法拷贝属性值为function的属性
所以如果只是想单纯复制一个嵌套对象,可以使用此方法
let foo = {
a:1,
b: {
c: 1
}
}
let bar = JSON.parse(JSON.stringify(foo));
来源:https://segmentfault.com/a/1190000010661297
es6入门
Object.assign方法用于对象的合并
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
如果只有一个参数,Object.assign会直接返回该参数。
const obj = {a:1};
Object.assign(obj) === obj //true
如果该参数不是对象,则会先转成对象,然后返回。
typeof Object.assign(2) // "object"
由于undefined和null无法转成对象,所以如果它们作为参数,就会报错。
Object.assign(undefined) // 报错
Object.assign(null) // 报错
如果非对象参数出现在源对象的位置(即非首参数),那么处理规则有所不同。首先,这些参数都会转成对象,如果无法转成对象,就会跳过。这意味着,如果undefined和null不在首参数,就不会报错。
let obj = {a: 1};
Object.assign(obj, undefined) === obj // true
Object.assign(obj, null) === obj // true
其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。
const v1 = 'abc';
const v2 = true;
const v3 = 10;
const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
Object.assign拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举属性。
Object.assign({b: 'c'},
Object.defineProperty({},'invisible',{
enumerable: false,
value: 'hello'
})
}
//{b: 'c'}
注意点
(1)浅拷贝
Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
const obj1 = {a: {b: 1}};
const obj2 = Object.assign({},obj1);
obj1.a.b = 2;
obj2.a.b //2
上面代码中,源对象obj1的a属性的值是一个对象,Object.assign拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面。
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
source1.b={m:6}
Object.assign(target,source1,source2)
target ={a: 1, b:{m:6}, c: 3}
target.b.m = 8; //修改m的值
source1.b.m //8 说明是浅拷贝
原因分析:string、boolean、number是按值传递的,array、obj是按引用传递。
(2)同名属性的替换
对于这种嵌套的对象,一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加。
const target = { a: { b: 'c', d: 'e' } }
const source = { a: { b: 'hello' } }
Object.assign(target, source)
// { a: { b: 'hello' } }
上面代码中,target对象的a属性被source对象的a属性整个替换掉了,而不会得到{ a: { b: 'hello', d: 'e' } }的结果。
这通常不是开发者想要的,需要特别小心。
一些函数库提供Object.assign的定制版本(比如 Lodash 的_.defaultsDeep方法),可以得到深拷贝的合并。
jquery的$.extend(true,{})可以进行深层合并
常见用途
Object.assign方法有很多用处
(1)为对象添加属性
class Point {
constructor (x,y) {
Object.assign(this,{x,y});
}
}
const source1 = {b:2};
const source2 = {c:3};
(2)为对象添加方法
Object.assign(SomeClass.prototype,{
someMethod(arg1,arg2) {
},
anotherMethod( ) {
}
})
//等同于
SomeClass.prototype.someMethod = function (arg1,arg2) {
};
SomeClass.prototype.anotherMethod = function ( ) {
};
上面代码使用了对象属性的简洁表示法,直接将两个函数放在大括号中,
再使用assign方法添加到SomeClass.prototype之中。
(3)克隆对象
function clone(origin) {
return Object.assign({},origin);
}
上面代码将原始对象拷贝到一个空对象,就得到了原始对象的克隆。
不过,用这种方法克隆,只能克隆原始对象自身的值(也就是浅拷贝),不能克隆它继承的值。如果想要保持继承链,可以采用下面的代码。
function clone(origin) {
let originProto = Object.getPrototypeOf(origin);
return Object.assign(Object.create(originProto),origin);
}
(4)合并多个对象
将多个对象合并到某个对象。
const merge = (target,...sources) => Object.assign(target,...sources);
如果希望合并后返回一个新对象,可以改写上面函数,对一个空对象合并。
const merge = (...sources) => Object.assign({},...sources);
(5)为属性指定默认值
const DEFAULTS = {
logLevel: 0,
outputFormat: 'html'
};
function processContent(options) {
options = Object.assign({},DEFAULTS,options);
console.log(options);
}
上面代码中,DEFAULTS对象是默认值,options对象是用户提供的参数。Object.assign方法将DEFAULTS和options合并成一个新对象,如果两者有同名属性,则option的属性值会覆盖DEFAULTS的属性值。
注意,由于存在浅拷贝的问题,DEFAULTS对象和options对象的所有属性的值,最好都是简单类型,不要指向另一个对象。否则,DEFAULTS对象的该属性很可能不起作用。
声明对象的时候,为了防止,声明的对象被覆盖,一般用const声明。
const DEFAULTS = {
url: {
host: 'example.com',
port: 7070
}
}
processContent({url:{port: 8080}})
//{ url: {port: 8000}}
上面代码的愿意是将url.port改成8000,url.host不变。实际结果却是options.url覆盖掉DEFAULTS.url,所以url.host就不存在了。
我的理解是,非嵌套类型,假如你这里说的是值类型。
值类型放在堆栈中,是只会存在一份的,谁用归谁。
引用类型,放在堆中,这时候,浅拷贝是拷贝了一份引用放在堆栈,深拷贝是拷贝了对象本身放在堆。
如何实现一个对象的深拷贝?
http://www.jb51.net/article/99013.htm
Object的常用方法和属性也是很常用的。
一、属性
Object自带一个prototype的属性,即Object.prototype, Object.prototype本身也只是一个对象,也会有一些属性和方法。
1.属性
Object.prototype.writable:默认为false
Object.prototype.enumerable:默认为false
Object.prototype.configurable:默认为false
Object.prototype.constructor:用于创建一个对象的原型。
2、常用方法
Object.prototype.hasOwnProperty():返回一个布尔值,表示某个对象是否含有指定的属性,而且此属性非原型链继承。
Object.prototype.isPrototypeOf():返回一个布尔值,表示指定的对象是否在本对象的原型链中。
Object.prototype.propertyIsEnumerable():判断指定属性是否可枚举。
Object.prototype.toString():返回对象的字符串表示。
Object.prototype.watch():给对象的某个属性增加监听。
Object.prototype.unwatch():移除对象某个属性的监听。
Object.prototype.valueOf():返回指定对象的原始值。
二、方法
Object.assign(target,...sources):把任意多的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。
Object.create(proto,[propertiesobject]):创建一个拥有指定原型和若干个指定属性的对象。
Object.defineProperty(obj,prop,descriptor):直接在一个对象上定义一个新属性,或者修改一个已经存在的属性,并返回这个对象。obj:需要定义属性的对象。prop:需定义或修改的属性的名字。descriptor:将被定义或修改的属性的描述符。
Object.entries(obj):返回一个包含由给定对象所有可枚举属性的属性名和属性值组成的 [属性名,属性值] 键值对的数组,数组中键值对的排列顺序和使用for…in循环遍历该对象时返回的顺序一致。
举例:
var obj = { foo: “bar”, baz: 42 };
console.log(Object.entries(obj)); // [ [‘foo’, ‘bar’], [‘baz’, 42] ]
Object.freeze(obj):冻结一个对象,冻结指的是不能向这个对象添加新的属性,不能修改其已有属性的值,不能删除已有属性,以及不能修改该对象已有属性的可枚举性、可配置性、可写性。也就是说,这个对象永远是不可变的。该方法返回被冻结的对象。
Object.getOwnPropertyDescriptor(obj, prop):返回指定对象上一个自有属性对应的属性描述符。
Object.getOwnPropertyNames(obj):返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性)组成的数组。
举例:
// 类数组对象
var obj = { 0: “a”, 1: “b”, 2: “c”};
console.log(Object.getOwnPropertyNames(obj).sort()); // [“0”, “1”, “2”]
Object.getPrototypeOf(object):返回该对象的原型。
Object.is(value1, value2):判断两个值是否是同一个值。
Object.isExtensible(obj):判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。
Object.isFrozen(obj):判断一个对象是否被冻结(frozen)。
Object.isSealed(obj):判断一个对象是否是密封的(sealed)。密封对象是指那些不可 扩展 的,且所有自身属性都不可配置的(non-configurable)且属性不可删除的对象(其可以是可写的)。
Object.keys(obj):返回一个由给定对象的所有可枚举自身属性的属性名组成的数组,数组中属性名的排列顺序和使用for-in循环遍历该对象时返回的顺序一致
举例:
var arr = [“a”, “b”, “c”];
alert(Object.keys(arr)); // 弹出”0,1,2”
// 类数组对象
var obj = { 0 : “a”, 1 : “b”, 2 : “c”};
alert(Object.keys(obj)); // 弹出”0,1,2”
Object.preventExtensions(obj):让一个对象变的不可扩展,也就是永远不能再添加新的属性。
Object.setPrototypeOf(obj, prototype):将一个指定的对象的原型设置为另一个对象或者null
Object.values(obj):返回一个包含指定对象所有的可枚举属性值的数组,数组中的值顺序和使用for…in循环遍历的顺序一样。
举例:
var obj = { foo: “bar”, baz: 42 };
console.log(Object.values(obj)); // [‘bar’, 42]
Object.assign()与深拷贝
深拷贝与浅拷贝
所谓深拷贝与浅拷贝,是围绕引用类型变量的拷贝进行的讨论。
在ecmascript中,变量分为基本类型和引用类型两种。其本质区别是不可变性,基本类型是不可变的,而引用类型是可变的。
所谓基本类型的不可变性,我们可以举个例子
let a = 1;
let b = 1;
a++;
a===2;//true
b===1;//true
声明一个变量a,并为其赋值1,这时内存中开辟出一片区域用来储存1。此时声明了一个变量b,也为b赋值1。当执行a++时,
基本类型的不可变性就体现出来,a++的值应该为2,但是这个值并不会将原来储存1的那片内存覆盖掉,而是再开辟一片内
存来存储2。所以对于这个1来讲,他是永远不可变的。
而对于引用变量则不同,因为其存储的是只想某个内存区域的地址,所以其修改时直接操作在内存上的,
这就导致了深拷贝和浅拷贝问题的出现。
浅拷贝
let foo = {
x: 1,
y: -1
}
let bar = foo;
foo.x++;
foo.x ===2 //true
bar.x ===2 //true
这就是最简单的浅拷贝,其效果十分明显,对拷贝源的操作,会直接体现在拷贝目标上,因为这个赋值行为的本质是内存地址的赋值,
所以他们指向了同一片内存区域。
浅拷贝十分容易,也十分常见,但却无法满足需求,加入我们需要获得拷贝源完全相同,却又不会互相影响的对象,应该怎么办呢
Object.assign()
ES6为我们提供了一种十分好用的方法,Object.assign(target, ...source)方法
assign方法接受多个参数,第一个参数target为拷贝目标,剩余参数...source是拷贝源。此方法可以将...source中的属性复制到target中,
同名属性会进行覆盖,并且在复制过程中实现了'伪'深拷贝
let foo = {
a: 1,
b: 2,
c: {
d: 1
}
}
let bar = { };
Object.assign(bar, foo);
foo.a++;
foo.a === 2 //true
bar.a === 1 //true
乍一看,好像已经实现了深拷贝的效果,对foo.a进行的操作并没有体现在bar.a中,但是再往后看
foo.c.d++;
foo.c.d === 2 //true
bar.c.d === 1 //false
bar.c.d === 2 //true
Object.assign()的拷贝类型十分明显了,这是一种可以对非嵌套对象进行深拷贝的方法,如果对象中出现嵌套情况,那么其对被嵌套对象的行为就成了普通的浅拷贝.
如果真的想进行深拷贝,最简单粗暴地方式就是JSON操作。
JSON对象中包含两个方法,stringify( )和parse( ),前者可以将对象JSON化,而后者可以将JSON格式转换为对象,这是一种可以实现深拷贝的方法。
但这种方法的缺陷是会破坏原型链,并且无法拷贝属性值为function的属性
所以如果只是想单纯复制一个嵌套对象,可以使用此方法
let foo = {
a:1,
b: {
c: 1
}
}
let bar = JSON.parse(JSON.stringify(foo));
来源:https://segmentfault.com/a/1190000010661297
es6入门
Object.assign方法用于对象的合并
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
如果只有一个参数,Object.assign会直接返回该参数。
const obj = {a:1};
Object.assign(obj) === obj //true
如果该参数不是对象,则会先转成对象,然后返回。
typeof Object.assign(2) // "object"
由于undefined和null无法转成对象,所以如果它们作为参数,就会报错。
Object.assign(undefined) // 报错
Object.assign(null) // 报错
如果非对象参数出现在源对象的位置(即非首参数),那么处理规则有所不同。首先,这些参数都会转成对象,如果无法转成对象,就会跳过。这意味着,如果undefined和null不在首参数,就不会报错。
let obj = {a: 1};
Object.assign(obj, undefined) === obj // true
Object.assign(obj, null) === obj // true
其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。
const v1 = 'abc';
const v2 = true;
const v3 = 10;
const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
Object.assign拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举属性。
Object.assign({b: 'c'},
Object.defineProperty({},'invisible',{
enumerable: false,
value: 'hello'
})
}
//{b: 'c'}
注意点
(1)浅拷贝
Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
const obj1 = {a: {b: 1}};
const obj2 = Object.assign({},obj1);
obj1.a.b = 2;
obj2.a.b //2
上面代码中,源对象obj1的a属性的值是一个对象,Object.assign拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面。
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
source1.b={m:6}
Object.assign(target,source1,source2)
target ={a: 1, b:{m:6}, c: 3}
target.b.m = 8; //修改m的值
source1.b.m //8 说明是浅拷贝
原因分析:string、boolean、number是按值传递的,array、obj是按引用传递。
(2)同名属性的替换
对于这种嵌套的对象,一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加。
const target = { a: { b: 'c', d: 'e' } }
const source = { a: { b: 'hello' } }
Object.assign(target, source)
// { a: { b: 'hello' } }
上面代码中,target对象的a属性被source对象的a属性整个替换掉了,而不会得到{ a: { b: 'hello', d: 'e' } }的结果。
这通常不是开发者想要的,需要特别小心。
一些函数库提供Object.assign的定制版本(比如 Lodash 的_.defaultsDeep方法),可以得到深拷贝的合并。
jquery的$.extend(true,{})可以进行深层合并
常见用途
Object.assign方法有很多用处
(1)为对象添加属性
class Point {
constructor (x,y) {
Object.assign(this,{x,y});
}
}
const source1 = {b:2};
const source2 = {c:3};
(2)为对象添加方法
Object.assign(SomeClass.prototype,{
someMethod(arg1,arg2) {
},
anotherMethod( ) {
}
})
//等同于
SomeClass.prototype.someMethod = function (arg1,arg2) {
};
SomeClass.prototype.anotherMethod = function ( ) {
};
上面代码使用了对象属性的简洁表示法,直接将两个函数放在大括号中,
再使用assign方法添加到SomeClass.prototype之中。
(3)克隆对象
function clone(origin) {
return Object.assign({},origin);
}
上面代码将原始对象拷贝到一个空对象,就得到了原始对象的克隆。
不过,用这种方法克隆,只能克隆原始对象自身的值(也就是浅拷贝),不能克隆它继承的值。如果想要保持继承链,可以采用下面的代码。
function clone(origin) {
let originProto = Object.getPrototypeOf(origin);
return Object.assign(Object.create(originProto),origin);
}
(4)合并多个对象
将多个对象合并到某个对象。
const merge = (target,...sources) => Object.assign(target,...sources);
如果希望合并后返回一个新对象,可以改写上面函数,对一个空对象合并。
const merge = (...sources) => Object.assign({},...sources);
(5)为属性指定默认值
const DEFAULTS = {
logLevel: 0,
outputFormat: 'html'
};
function processContent(options) {
options = Object.assign({},DEFAULTS,options);
console.log(options);
}
上面代码中,DEFAULTS对象是默认值,options对象是用户提供的参数。Object.assign方法将DEFAULTS和options合并成一个新对象,如果两者有同名属性,则option的属性值会覆盖DEFAULTS的属性值。
注意,由于存在浅拷贝的问题,DEFAULTS对象和options对象的所有属性的值,最好都是简单类型,不要指向另一个对象。否则,DEFAULTS对象的该属性很可能不起作用。
声明对象的时候,为了防止,声明的对象被覆盖,一般用const声明。
const DEFAULTS = {
url: {
host: 'example.com',
port: 7070
}
}
processContent({url:{port: 8080}})
//{ url: {port: 8000}}
上面代码的愿意是将url.port改成8000,url.host不变。实际结果却是options.url覆盖掉DEFAULTS.url,所以url.host就不存在了。
我的理解是,非嵌套类型,假如你这里说的是值类型。
值类型放在堆栈中,是只会存在一份的,谁用归谁。
引用类型,放在堆中,这时候,浅拷贝是拷贝了一份引用放在堆栈,深拷贝是拷贝了对象本身放在堆。
如何实现一个对象的深拷贝?
http://www.jb51.net/article/99013.htm
收起阅读 »分享一个带登录的 demo,解决了双webview导航启动时,首页加载卡顿的问题
研究了好久的首页启动白屏卡顿现象,虽然只有那么短短的几毫秒,但是看着总是不舒服,尝试了官方推荐的nativeTab_demo。
但是不想用那个小凸点的半圆球,改了好久都没有解决问题,无奈还是选用了这个,然后就在官方的代码上继续修改,终于找到突破口,略微修改了一下代码,实现了启动的时候 加载首页可以像nativeTab_demo那么迅速显示,不卡顿,而且可以少加载一个页面,直接加载的首页可以显示,不用再定义main页面。
所以也想分享给各位在坑中挣扎的兄弟们。这个是我准备给公司开发的APP用的,就直接分享基础版的东西给你们吧。代码写的不好,大神勿喷。
demo在下面,自己下载吧!
mui.init();
/**此处是有四个菜单,首页不需要在这里创建,默认访问的首页会自动创建出来,所以tuan.html算第二个菜单开始**/
var subPages = ['tuan.html','small.html','person.html'];
mui.plusReady(function(){
//创建主体和子页面
var sub0 = plus.webview.getWebviewById(subPages[0]);
var sub1 = plus.webview.getWebviewById(subPages[1]);
var sub2 = plus.webview.getWebviewById(subPages[2]);
/**此处保证页面只有第一次运行的时候去创建那几个子页面**/
if(!sub0 || !sub1 || !sub2){
var self = plus.webview.currentWebview();
for(var i=0;i<3;i++){
var sub = plus.webview.create(
subPages[i],
subPages[i],
{
top:'0px',
bottom:'51px'
});
}
/**此处不写hide()方法也是可以的,运行时,其他页面创建出来后都在首页后面被遮住了好像,具体我也没懂**/
}
mui(".mui-bar-tab").on("tap","a",function(e){
var tagPage = this.getAttribute("href");
/**此处如果是点击首页,就隐藏其他页面,就会显示首页了**/
if(tagPage == 'index.html'){
for(var j=0;j<3;j++){
plus.webview.getWebviewById(subPages[j]).hide();
}
}else{
plus.webview.show(tagPage,"fade-in",300);
}
})
});
写的不好,多多指教!还有一个问题,双webview模式去加载的时候,每次点击列表进入详情页面的时候会执行两次 id 传值,是因为加载了两次 mui.js 这个问题我还没有解决,有解决办法的可以在下面留言给我,谢谢!
demo在下面,自己下载吧!
研究了好久的首页启动白屏卡顿现象,虽然只有那么短短的几毫秒,但是看着总是不舒服,尝试了官方推荐的nativeTab_demo。
但是不想用那个小凸点的半圆球,改了好久都没有解决问题,无奈还是选用了这个,然后就在官方的代码上继续修改,终于找到突破口,略微修改了一下代码,实现了启动的时候 加载首页可以像nativeTab_demo那么迅速显示,不卡顿,而且可以少加载一个页面,直接加载的首页可以显示,不用再定义main页面。
所以也想分享给各位在坑中挣扎的兄弟们。这个是我准备给公司开发的APP用的,就直接分享基础版的东西给你们吧。代码写的不好,大神勿喷。
demo在下面,自己下载吧!
mui.init();
/**此处是有四个菜单,首页不需要在这里创建,默认访问的首页会自动创建出来,所以tuan.html算第二个菜单开始**/
var subPages = ['tuan.html','small.html','person.html'];
mui.plusReady(function(){
//创建主体和子页面
var sub0 = plus.webview.getWebviewById(subPages[0]);
var sub1 = plus.webview.getWebviewById(subPages[1]);
var sub2 = plus.webview.getWebviewById(subPages[2]);
/**此处保证页面只有第一次运行的时候去创建那几个子页面**/
if(!sub0 || !sub1 || !sub2){
var self = plus.webview.currentWebview();
for(var i=0;i<3;i++){
var sub = plus.webview.create(
subPages[i],
subPages[i],
{
top:'0px',
bottom:'51px'
});
}
/**此处不写hide()方法也是可以的,运行时,其他页面创建出来后都在首页后面被遮住了好像,具体我也没懂**/
}
mui(".mui-bar-tab").on("tap","a",function(e){
var tagPage = this.getAttribute("href");
/**此处如果是点击首页,就隐藏其他页面,就会显示首页了**/
if(tagPage == 'index.html'){
for(var j=0;j<3;j++){
plus.webview.getWebviewById(subPages[j]).hide();
}
}else{
plus.webview.show(tagPage,"fade-in",300);
}
})
});
写的不好,多多指教!还有一个问题,双webview模式去加载的时候,每次点击列表进入详情页面的时候会执行两次 id 传值,是因为加载了两次 mui.js 这个问题我还没有解决,有解决办法的可以在下面留言给我,谢谢!
demo在下面,自己下载吧!
收起阅读 »调用QQAPP进行聊天
// str 为qq号码
function(str){
if(plus.os.name == "Android") {
var openUrl = 'mqqwpa://im/chat?chat_type=wpa&uin=' + str;
plus.nativeUI.showWaiting();
var main = plus.android.runtimeMainActivity();
var Intent = plus.android.importClass('android.content.Intent');
var Uri = plus.android.importClass('android.net.Uri');
var intent = new Intent(Intent.ACTION_VIEW, Uri.parse(openUrl));
main.startActivity(intent);
}
if(plus.os.name == "iOS") {
var openUrl = 'mqq://im/chat?chat_type=wpa&uin=' + str + '&version=1&src_type=web';
plus.runtime.launchApplication({
action: openUrl
}, function(e) {
plus.nativeUI.confirm("检查到您未安装qq,请先到appstore搜索下载?", function(i) {
if(i.index == 0) {
iosAppstore("itunes.apple.com/cn/app/mqq/");
}
});
});
}
} // str 为qq号码
function(str){
if(plus.os.name == "Android") {
var openUrl = 'mqqwpa://im/chat?chat_type=wpa&uin=' + str;
plus.nativeUI.showWaiting();
var main = plus.android.runtimeMainActivity();
var Intent = plus.android.importClass('android.content.Intent');
var Uri = plus.android.importClass('android.net.Uri');
var intent = new Intent(Intent.ACTION_VIEW, Uri.parse(openUrl));
main.startActivity(intent);
}
if(plus.os.name == "iOS") {
var openUrl = 'mqq://im/chat?chat_type=wpa&uin=' + str + '&version=1&src_type=web';
plus.runtime.launchApplication({
action: openUrl
}, function(e) {
plus.nativeUI.confirm("检查到您未安装qq,请先到appstore搜索下载?", function(i) {
if(i.index == 0) {
iosAppstore("itunes.apple.com/cn/app/mqq/");
}
});
});
}
} 收起阅读 »
视频播放方面官方没有吗?
强烈,希望官方视频播放方的组建,UI?
现在视频这么火,没有视频方面的APP,简直是开玩笑。。。
强烈,希望官方视频播放方的组建,UI?
现在视频这么火,没有视频方面的APP,简直是开玩笑。。。
《webkit技术内幕》读书笔记
第一章 浏览器和浏览器内核
1.渲染引擎的组成:HTML解释器、css解释器、布局(Layout)、JS引擎、绘图等
-
Blink和Webkit的不同:
》跨进程的iframe:为了解决iframe潜在的安全问题,为iframe创建一个单独的沙箱进程
》将DOM引入JS引擎中,提升js访问DOM的性能
》 接口的修改、性能优化等
3.浏览器和对应的渲染引擎:
TridentGecko Webkit IE Firefox Safari Chrome Android浏览器 ChromeOS等 第二章 HTML网页和结构
1.webkit渲染过程
》通过资源加载器加载URL对应的网页
》网页被交给HTML解释器转变成Token
》HTML解释器依据Token构建节点,形成DOM树
》如果节点是js代码,调用js引擎解释执行
》如果节点是图片、css、视频等资源,会调用资源加载器来加载他们,因为他们是异步的,不会阻碍当前DOM树的继续创建;如果是js资源,
则需要停止当前DOM树的创建,直到js资源加载并被执行后才会继续DOM的创建。
第三章 Webkit架构和模块
1.浏览器是多进程的,包括:Browser进程、每个网页的Render进程、插件进程、GPU进程等进程:
》Browser进程和页面渲染是分开的,这保证了页面渲染的崩溃不会导致浏览器主界面的崩溃。
》每个页面是独立的进程,保证了页面之间相互不影响。
》插件的进程问题不会影响浏览器主界面和网页
》GPU加速也是独立的
2.浏览器中的进程基本上是多线程的,如Browser进程包括UI线程和I/O线程等,网页的Render进程包括渲染线程和I/O线程等,GPU进程包括I/O线程和GL线程等:
》 Browser进程收到用户的请求,首先交由UI线程处理,而且将相应的任务交给I/O线程,它随即将该任务传递给Render进程。
》Render进程的I/O线程经过简单的解释后交给渲染线程,渲染线程接受请求,加载网页并渲染网页,这其中需要Browser进程获取资源和需要GPU进程来帮助渲染。最后Render进程将结构有I/O线程传递给Browser进程。
》Browser进程接收到结果并将结果绘制出来。
第四章 资源加载和网络栈 - 资源加载是一个很耗时的过程。异步执行资源(如图片、css等)的加载和执行是不会影响当前Webkit的渲染过程。同步执行的js文件会阻塞主线程的渲染过程,这会严重影响Webkit下载资源的效率;因为后面还有需要要下载的资源。这种情况下Webkit会启动另外一个线程去遍历后面的HTML网页,收集需要的资源URL,然后发送请求,这样可以避免被阻塞。与此同时,Webkit能够并发下载这些资源,甚至并发下载js代码,这种机制对于网页的加载提速是很明显的。
2.渲染引擎、js引擎、js加载三者是互斥的?至少前两者是的。
3.由于安全(Render进程是没有权限去获取资源的)和效率上的考虑,Render进程的资源获取实际上是通过进程间通信将任务交给Browser进程来完成,Browser进程有权限从网络和本地获取资源。 - Chroumium的本地缓存包括一个索引文件和四个数据文件(如果缓存比较大,那么是一个数据文件)。
- SDPY协议是HTTP2的基础,它的核心思想是多路复用,仅使用一个连接链传输一个网页中的众多资源:
- 引入新的压缩技术(一方面使用gzip或compress压缩后再发送;另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号)
第五章 HTML解释器和DOM模型
1.HTML解释器的流程:字节流(bytes)->字符流(characters)->词语(token)->Xss验证(可选)->节点->DOM树。 DOM树的创建只能在渲染线程上创建和访问,从字符串到词语这个阶段可以交给单独的线程来做。
- 当渲染引擎接收到一个事件的时候,它会通过HitTest(Webkit中一种检查触发事件在哪个区域的算法)检查哪个元素是直接的事件目标。
- 影子(Shadow)DOM:如<vedio>标签,其内部功能是很复杂的,但是对外只暴漏一个html标签;相当于对节点内部封装,外部看到的有限,但是内部逻辑是很复杂的。它主要解决了一个文档中可能需要大量交互的多个DOM树建立和维护各自功能边界的问题。 createShadowRoot是js中提供的创建影子DOM的方法。
第六章 CSS解释器和样式布局
1.CSS的样式规则、选择器(标签、类型、ID、属性、后代、子女、相邻同胞)、盒模型、包含块(Containing Block)模型、样式属性(背景、文本、字体、列表、表格、定位)。
- CSSOM:CSS对象模型。它的思想是在DOM中的一些节点接口中加入获取和操作CSS属性或者接口的js接口
- 布局计算是一个递归的过程,这是因为一个节点的大小通常需要计算它的子女节点的位置、大小等信息。当首次加载、viewport大小改变、动画以及通过js改变样式信息时都会触发重新计算布局。
第七章 渲染基础
1.Webkit的布局计算使用RenderObject树并保存计算结果到RenderObject树中,RenderObject树同其他树(如:RenderLayer树)构成Webkit渲染的主要基础设施。 - 为一个DOM树节点创建RenderObject对象的规则:
》DOM书中的document节点
》DOM树中的可视节点,如html、body、div等。Webkit不会为非可视化节点(如meta标签)创建RenderObject对象
》某些情况下Webkit需要建立匿名RenderObject节点,该节点不对应于DOM树种的任何节点,而是为了Webkit处理的需要
》RenderObject树节点和DOM树节点不是一一对应的关系
3.为RenderObject节点创建新的RenderLayer节点的规则是:(不同于css3硬件加速中的复合图层)
》document节点
》document的子女节点,也就是html节点对应的RenderBlock节点
》显式的指定CSS位置的RenderObject节点
》有透明效果的RenderObject节点
》节点有溢出(overflow)、alpha或者反射等效果的RenderObject节点
》使用Canvas 2D或 3D(webGL)技术的RenderObject节点
》Vedio节点对应的RenderObject节点
4.Webkit的渲染方式主要包括三种:
》软件渲染:绘图操作由CPU来完成
》硬件加速渲染:绘图操作由GPU来完成
》混合模式: 多个层的渲染结果合并到一个图像中,称之为合成渲染
硬件渲染比较适合于3D绘图。2D绘图时GPU不一定比使用CPU有优势,主要因为:
1.CPU的缓存机制有效的减少了重复绘制;
2.GPU资源相对CPU的内存来说比较紧张;- 软件渲染对待更新区域的处理可能优化到只需计算一个极小的区域,而硬件渲染可能需要重新绘制一层或则多层。
5.软件渲染结果基本上存储在CPU内存的一块区域,多数情况下是一个位图(Bitmap);存储结果会被copy到Browser的存储空间,然后通过Browser进程渲染出来。
第八章 硬件加速机制
1.GPU主要用来绘制3D图形,而且性能很好。GPU不能像软件渲染那样只计算其中更新的区域,需重绘所有区域;因此为了提高GPU的性能,需要对网页进行分层。分层后,部分区域的更新只在网页的一层或者几层,而不需将整个网页重绘;绘制完成后把层合成起来,即利用了GPU能力,又能减少不必要的重绘开销。
2.硬件加速机制在RenderLayer树建立后需要做三件事情来完成网页渲染:
》Webkit决定将哪些RenderLayer对象组合在一起,形成一个有后端存储的新层,这一新层不久后用户之后的合成(Compositing),这里称之为合成层(Compositing Layer)
》将每个合成层包含的这些RenderLayer内容绘制在合成层的后端存储中
》合成器将这些合成层合成起来,形成网页的最终可视化结果
3.RenderLayer对象具有以下特征之一,那么它就是合成层(参考css3硬件加速中的复合图层):
》RenderLayer 具有CSS 3D属性或者 CSS 透视效果
》RenderLayer 包含的 RenderObject 节点表示的是使用硬件加速的视频解码技术的vedio元素
》RenderLayer 包含的 RenderObject 节点表示的是使用硬件加速的Canvas 2D元素或者 WebGL 技术
》RenderLayer 使用了CSS透明效果的动画或者CSS变换的动画
》RenderLayer 使用了硬件加速的 CSS FIlter 技术
》RenderLayer 使用了剪裁(clip) 或者反射(Reflection) 属性,并且它的后代中包含一个合成层
》RenderLayer 有一个Z坐标比自己小的兄弟节点,且该节点是一个合成层
4.硬件加速最终会调用OpenGL/OpenGLES库。GPU进程最终绘制的结果不再像软件渲染那样通过共享内存传递给Browser进程,而是直接将页面的内容绘制在浏览器的标签窗口内。
5.页面加载后进行绘制时,会经历计算布局、绘图和合成三个阶段,前两者耗时较多。鉴于此提升浏览器渲染性能的两者方法:
》使用合适的网页分层技术以减少需要重新计算的布局和绘图
》使用CSS 3D 变形和动画技术:浏览器不需要重新布局,也不需要重新绘图,只需使用合成功能,而合成功能耗时非常少。
第九章 JavaScript引擎
1.解释性语言(如 js)和编译型语言(如 java c++)的区别:编译确定位置、偏移信息共享以及偏移信息查找。 三者概括起来讲是因为编译型语言可以在编译的时候确定指针位置,而解释性语言只有在执行的时候才会确定。如果找js的属性只能通过属性名匹配去查找,而c++中可以根据偏移位置去直接找到。
2.一个js引擎要包括以下几部分:
》编译器:将源代码编译成抽象语法树,某些引擎中还包括将抽象语法树转成字节码
》解释器:某些引擎中,解释器主要接受字节码,解释执行这个字节码,同时也依赖垃圾回收机制
》JIT工具:(Just-In-Time技术) 它的主要思想是当解释器将源代码解释成内部表示的时候(java字节码就是一个典型的例子),js的执行环境不仅是解释这些内部表示,而且将其中的一些字节码(主要是使用效率高的部分)转成本地代码(汇编代码),这样可以被CPU直接执行,而不是解释执行,从而极大地提高性能。
》垃圾回收器和分析工具:它们负责垃圾回收和手机引擎中的信息,帮助改善引擎的性能和功效
3.引擎的流程:
源代码 -> 抽象语法树 -> 字节码 -> 解释器 -> JIT -> 本地代码
4.v8引擎的工作原理
》数据表示: 数据和句柄,句柄指向数据存储地址。当进行垃圾回收的时候,不需要移动数据(开销大),只需修改句柄中的指针。
》V8不会把抽象语法树转成字节码或者其他中间表示,而是通过JIT直接生成本地代码。 优点:减少了抽象语法树到字节码的转换时间。 缺点:减少了中间表示(字节码)可能的优化机会、有些场景没必要生成本地代码(过度优化)
》优化回滚:编译器认为某些代码比较稳定,变量类型不会发生改变,然后生成高效的本地代码;如果引擎发现变量类型发生变化,需要使用一种机制将它做的这些错误决定回滚到之前的一般情况,这个过程就是优化回滚。优化回滚是一个很费时的操作,所以能够不回滚,肯定不要回滚。
var count = 0;
function ABC(){
count++;
if(count < 1000000){
return 123;
}
return new Date();
}
》隐藏类和内嵌缓存:V8使用类和偏移位置思想,将本来需要通过字符串匹配来查找属性值的算法改进为使用类似C++编译器的偏移位置机制来实现,这就是隐藏类
function ABC(x, y){
this.x = x;
this.y = y;
}
var a = new ABC(1,1);
var b = new ABC(1,2);
上述例子中的a、b两个对象包含相同的属性名,因此它们被归为同一个组,也就是隐藏类,这些属性在隐藏类中有相同的偏移值。当访问这些对象的属性时,可以通过偏移值知道它们的位置并进行访问。
内嵌缓存的基本思想是将使用之前查找的结果缓存起来,也就是说V8可以将之前查找的隐藏类和偏移值保存下来。当下次查找的时候,首先比较当前对象是否也是之前的隐藏类,如果是的话,可以直接使用之前的偏移值,从而减少查找表的时间。
》内存管理:V8采用的分代垃圾回收机制,分为年轻分代、年老分代等。
》快照机制:快照机制就是将这些内置的对象和函数(如 Math、String、Array)加载之后的内存保存并被序列化。序列化之后的结果很容易被反序列化,经过快照机制的启动时间,可以缩减几毫秒。
5.JavaScriptCore和V8不同,它会生成平台无关的字节码(和java类似),然后基于此做优化,同时它的句柄不论在32位还是64位平台上,都使用64位来表示。
- 编写高效的JavaScript代码:
》类型:把相同类型的元素放到一个数组中,这样引擎可以通过偏移位置来访问他们
》数据表示:简单类型的数据可以直接保存在句柄中,这样可以有效地减少寻址时间和内存的使用。对于数值来说,能使用证书的尽量不要使用浮点数。
》内存:及时触发垃圾回收,可以把不再使用的对象的变量设置为空:a.x=null,或者通过delete关键字进行删除:delete a.x ;但是后者有可能会由于使用了隐藏类,需要创建新的隐藏类,进而带来一些复杂的额外操作。
》优化回滚:不要写触发优化回滚的代码
》新机制:如通过requestAnimationFrame替代setInterval实现动画等。
作者:狐尼克朱迪
链接:https://www.jianshu.com/p/506447128ed4
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
css3硬件加速
https://www.jianshu.com/p/f8b1d6e598db
第一章 浏览器和浏览器内核
1.渲染引擎的组成:HTML解释器、css解释器、布局(Layout)、JS引擎、绘图等
-
Blink和Webkit的不同:
》跨进程的iframe:为了解决iframe潜在的安全问题,为iframe创建一个单独的沙箱进程
》将DOM引入JS引擎中,提升js访问DOM的性能
》 接口的修改、性能优化等
3.浏览器和对应的渲染引擎:
TridentGecko Webkit IE Firefox Safari Chrome Android浏览器 ChromeOS等 第二章 HTML网页和结构
1.webkit渲染过程
》通过资源加载器加载URL对应的网页
》网页被交给HTML解释器转变成Token
》HTML解释器依据Token构建节点,形成DOM树
》如果节点是js代码,调用js引擎解释执行
》如果节点是图片、css、视频等资源,会调用资源加载器来加载他们,因为他们是异步的,不会阻碍当前DOM树的继续创建;如果是js资源,
则需要停止当前DOM树的创建,直到js资源加载并被执行后才会继续DOM的创建。
第三章 Webkit架构和模块
1.浏览器是多进程的,包括:Browser进程、每个网页的Render进程、插件进程、GPU进程等进程:
》Browser进程和页面渲染是分开的,这保证了页面渲染的崩溃不会导致浏览器主界面的崩溃。
》每个页面是独立的进程,保证了页面之间相互不影响。
》插件的进程问题不会影响浏览器主界面和网页
》GPU加速也是独立的
2.浏览器中的进程基本上是多线程的,如Browser进程包括UI线程和I/O线程等,网页的Render进程包括渲染线程和I/O线程等,GPU进程包括I/O线程和GL线程等:
》 Browser进程收到用户的请求,首先交由UI线程处理,而且将相应的任务交给I/O线程,它随即将该任务传递给Render进程。
》Render进程的I/O线程经过简单的解释后交给渲染线程,渲染线程接受请求,加载网页并渲染网页,这其中需要Browser进程获取资源和需要GPU进程来帮助渲染。最后Render进程将结构有I/O线程传递给Browser进程。
》Browser进程接收到结果并将结果绘制出来。
第四章 资源加载和网络栈 - 资源加载是一个很耗时的过程。异步执行资源(如图片、css等)的加载和执行是不会影响当前Webkit的渲染过程。同步执行的js文件会阻塞主线程的渲染过程,这会严重影响Webkit下载资源的效率;因为后面还有需要要下载的资源。这种情况下Webkit会启动另外一个线程去遍历后面的HTML网页,收集需要的资源URL,然后发送请求,这样可以避免被阻塞。与此同时,Webkit能够并发下载这些资源,甚至并发下载js代码,这种机制对于网页的加载提速是很明显的。
2.渲染引擎、js引擎、js加载三者是互斥的?至少前两者是的。
3.由于安全(Render进程是没有权限去获取资源的)和效率上的考虑,Render进程的资源获取实际上是通过进程间通信将任务交给Browser进程来完成,Browser进程有权限从网络和本地获取资源。 - Chroumium的本地缓存包括一个索引文件和四个数据文件(如果缓存比较大,那么是一个数据文件)。
- SDPY协议是HTTP2的基础,它的核心思想是多路复用,仅使用一个连接链传输一个网页中的众多资源:
- 引入新的压缩技术(一方面使用gzip或compress压缩后再发送;另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号)
第五章 HTML解释器和DOM模型
1.HTML解释器的流程:字节流(bytes)->字符流(characters)->词语(token)->Xss验证(可选)->节点->DOM树。 DOM树的创建只能在渲染线程上创建和访问,从字符串到词语这个阶段可以交给单独的线程来做。
- 当渲染引擎接收到一个事件的时候,它会通过HitTest(Webkit中一种检查触发事件在哪个区域的算法)检查哪个元素是直接的事件目标。
- 影子(Shadow)DOM:如<vedio>标签,其内部功能是很复杂的,但是对外只暴漏一个html标签;相当于对节点内部封装,外部看到的有限,但是内部逻辑是很复杂的。它主要解决了一个文档中可能需要大量交互的多个DOM树建立和维护各自功能边界的问题。 createShadowRoot是js中提供的创建影子DOM的方法。
第六章 CSS解释器和样式布局
1.CSS的样式规则、选择器(标签、类型、ID、属性、后代、子女、相邻同胞)、盒模型、包含块(Containing Block)模型、样式属性(背景、文本、字体、列表、表格、定位)。
- CSSOM:CSS对象模型。它的思想是在DOM中的一些节点接口中加入获取和操作CSS属性或者接口的js接口
- 布局计算是一个递归的过程,这是因为一个节点的大小通常需要计算它的子女节点的位置、大小等信息。当首次加载、viewport大小改变、动画以及通过js改变样式信息时都会触发重新计算布局。
第七章 渲染基础
1.Webkit的布局计算使用RenderObject树并保存计算结果到RenderObject树中,RenderObject树同其他树(如:RenderLayer树)构成Webkit渲染的主要基础设施。 - 为一个DOM树节点创建RenderObject对象的规则:
》DOM书中的document节点
》DOM树中的可视节点,如html、body、div等。Webkit不会为非可视化节点(如meta标签)创建RenderObject对象
》某些情况下Webkit需要建立匿名RenderObject节点,该节点不对应于DOM树种的任何节点,而是为了Webkit处理的需要
》RenderObject树节点和DOM树节点不是一一对应的关系
3.为RenderObject节点创建新的RenderLayer节点的规则是:(不同于css3硬件加速中的复合图层)
》document节点
》document的子女节点,也就是html节点对应的RenderBlock节点
》显式的指定CSS位置的RenderObject节点
》有透明效果的RenderObject节点
》节点有溢出(overflow)、alpha或者反射等效果的RenderObject节点
》使用Canvas 2D或 3D(webGL)技术的RenderObject节点
》Vedio节点对应的RenderObject节点
4.Webkit的渲染方式主要包括三种:
》软件渲染:绘图操作由CPU来完成
》硬件加速渲染:绘图操作由GPU来完成
》混合模式: 多个层的渲染结果合并到一个图像中,称之为合成渲染
硬件渲染比较适合于3D绘图。2D绘图时GPU不一定比使用CPU有优势,主要因为:
1.CPU的缓存机制有效的减少了重复绘制;
2.GPU资源相对CPU的内存来说比较紧张;- 软件渲染对待更新区域的处理可能优化到只需计算一个极小的区域,而硬件渲染可能需要重新绘制一层或则多层。
5.软件渲染结果基本上存储在CPU内存的一块区域,多数情况下是一个位图(Bitmap);存储结果会被copy到Browser的存储空间,然后通过Browser进程渲染出来。
第八章 硬件加速机制
1.GPU主要用来绘制3D图形,而且性能很好。GPU不能像软件渲染那样只计算其中更新的区域,需重绘所有区域;因此为了提高GPU的性能,需要对网页进行分层。分层后,部分区域的更新只在网页的一层或者几层,而不需将整个网页重绘;绘制完成后把层合成起来,即利用了GPU能力,又能减少不必要的重绘开销。
2.硬件加速机制在RenderLayer树建立后需要做三件事情来完成网页渲染:
》Webkit决定将哪些RenderLayer对象组合在一起,形成一个有后端存储的新层,这一新层不久后用户之后的合成(Compositing),这里称之为合成层(Compositing Layer)
》将每个合成层包含的这些RenderLayer内容绘制在合成层的后端存储中
》合成器将这些合成层合成起来,形成网页的最终可视化结果
3.RenderLayer对象具有以下特征之一,那么它就是合成层(参考css3硬件加速中的复合图层):
》RenderLayer 具有CSS 3D属性或者 CSS 透视效果
》RenderLayer 包含的 RenderObject 节点表示的是使用硬件加速的视频解码技术的vedio元素
》RenderLayer 包含的 RenderObject 节点表示的是使用硬件加速的Canvas 2D元素或者 WebGL 技术
》RenderLayer 使用了CSS透明效果的动画或者CSS变换的动画
》RenderLayer 使用了硬件加速的 CSS FIlter 技术
》RenderLayer 使用了剪裁(clip) 或者反射(Reflection) 属性,并且它的后代中包含一个合成层
》RenderLayer 有一个Z坐标比自己小的兄弟节点,且该节点是一个合成层
4.硬件加速最终会调用OpenGL/OpenGLES库。GPU进程最终绘制的结果不再像软件渲染那样通过共享内存传递给Browser进程,而是直接将页面的内容绘制在浏览器的标签窗口内。
5.页面加载后进行绘制时,会经历计算布局、绘图和合成三个阶段,前两者耗时较多。鉴于此提升浏览器渲染性能的两者方法:
》使用合适的网页分层技术以减少需要重新计算的布局和绘图
》使用CSS 3D 变形和动画技术:浏览器不需要重新布局,也不需要重新绘图,只需使用合成功能,而合成功能耗时非常少。
第九章 JavaScript引擎
1.解释性语言(如 js)和编译型语言(如 java c++)的区别:编译确定位置、偏移信息共享以及偏移信息查找。 三者概括起来讲是因为编译型语言可以在编译的时候确定指针位置,而解释性语言只有在执行的时候才会确定。如果找js的属性只能通过属性名匹配去查找,而c++中可以根据偏移位置去直接找到。
2.一个js引擎要包括以下几部分:
》编译器:将源代码编译成抽象语法树,某些引擎中还包括将抽象语法树转成字节码
》解释器:某些引擎中,解释器主要接受字节码,解释执行这个字节码,同时也依赖垃圾回收机制
》JIT工具:(Just-In-Time技术) 它的主要思想是当解释器将源代码解释成内部表示的时候(java字节码就是一个典型的例子),js的执行环境不仅是解释这些内部表示,而且将其中的一些字节码(主要是使用效率高的部分)转成本地代码(汇编代码),这样可以被CPU直接执行,而不是解释执行,从而极大地提高性能。
》垃圾回收器和分析工具:它们负责垃圾回收和手机引擎中的信息,帮助改善引擎的性能和功效
3.引擎的流程:
源代码 -> 抽象语法树 -> 字节码 -> 解释器 -> JIT -> 本地代码
4.v8引擎的工作原理
》数据表示: 数据和句柄,句柄指向数据存储地址。当进行垃圾回收的时候,不需要移动数据(开销大),只需修改句柄中的指针。
》V8不会把抽象语法树转成字节码或者其他中间表示,而是通过JIT直接生成本地代码。 优点:减少了抽象语法树到字节码的转换时间。 缺点:减少了中间表示(字节码)可能的优化机会、有些场景没必要生成本地代码(过度优化)
》优化回滚:编译器认为某些代码比较稳定,变量类型不会发生改变,然后生成高效的本地代码;如果引擎发现变量类型发生变化,需要使用一种机制将它做的这些错误决定回滚到之前的一般情况,这个过程就是优化回滚。优化回滚是一个很费时的操作,所以能够不回滚,肯定不要回滚。
var count = 0;
function ABC(){
count++;
if(count < 1000000){
return 123;
}
return new Date();
}
》隐藏类和内嵌缓存:V8使用类和偏移位置思想,将本来需要通过字符串匹配来查找属性值的算法改进为使用类似C++编译器的偏移位置机制来实现,这就是隐藏类
function ABC(x, y){
this.x = x;
this.y = y;
}
var a = new ABC(1,1);
var b = new ABC(1,2);
上述例子中的a、b两个对象包含相同的属性名,因此它们被归为同一个组,也就是隐藏类,这些属性在隐藏类中有相同的偏移值。当访问这些对象的属性时,可以通过偏移值知道它们的位置并进行访问。
内嵌缓存的基本思想是将使用之前查找的结果缓存起来,也就是说V8可以将之前查找的隐藏类和偏移值保存下来。当下次查找的时候,首先比较当前对象是否也是之前的隐藏类,如果是的话,可以直接使用之前的偏移值,从而减少查找表的时间。
》内存管理:V8采用的分代垃圾回收机制,分为年轻分代、年老分代等。
》快照机制:快照机制就是将这些内置的对象和函数(如 Math、String、Array)加载之后的内存保存并被序列化。序列化之后的结果很容易被反序列化,经过快照机制的启动时间,可以缩减几毫秒。
5.JavaScriptCore和V8不同,它会生成平台无关的字节码(和java类似),然后基于此做优化,同时它的句柄不论在32位还是64位平台上,都使用64位来表示。
- 编写高效的JavaScript代码:
》类型:把相同类型的元素放到一个数组中,这样引擎可以通过偏移位置来访问他们
》数据表示:简单类型的数据可以直接保存在句柄中,这样可以有效地减少寻址时间和内存的使用。对于数值来说,能使用证书的尽量不要使用浮点数。
》内存:及时触发垃圾回收,可以把不再使用的对象的变量设置为空:a.x=null,或者通过delete关键字进行删除:delete a.x ;但是后者有可能会由于使用了隐藏类,需要创建新的隐藏类,进而带来一些复杂的额外操作。
》优化回滚:不要写触发优化回滚的代码
》新机制:如通过requestAnimationFrame替代setInterval实现动画等。
作者:狐尼克朱迪
链接:https://www.jianshu.com/p/506447128ed4
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
css3硬件加速
https://www.jianshu.com/p/f8b1d6e598db
jquery $.extend()用法总结
jquery为开发插件提供了两个方法,分别是:
jQuery.fn.extend(object);
jQuery.extend(object);
jQuery.fn.extend(object);给jQuery对象添加方法。
jQuery.extend(object);为扩展jQuery类本身.为类添加新的方法。
注意用法上的区别
jQuery.fn.myPlugin的用法为 $('.ye').myPlugin
jQuery.extend的用法为 $.add(3,5);
jQuery.fn.myPlugin = function(options) {
$options = $.extend({
html: "no messages",
css : {
"color":"red",
"font-size": "14px"
}},
options);
return $(this).css({
"color": $options.css.color
}).html($options.html);
}
$('.ye').myPlugin({
html: "so esay,yes?",
css: {"color":"green","font-size":"20px"}
});
1.合并多个对象
这是使用的就是$.extend( )的嵌套多个对象的功能。
所谓嵌套多个对象,有点类似于数组的合并的操作。但是这里是对象。举例说明:
用法 jQuery.extend(obj1,obj2,obj3...)
var css1 = {size:"10px",style: "oblique"};
var css2 = {size:"12px",style:"oplique",weight:"bolder"};
jQuery.extend(css1,css2);
//结果:css1的size属性被覆盖,而且继承了css2的weight属性
//css1 = {size: "12px",style: “oblique” ,weight: "bolder"}
2.深度嵌套对象
jQuery.extend(
{name: "John",location: {city: "Boston"}},
{last: "Resig",location:{state: "MA"}}
);
//结果
// => {name:"John",last: "Resig",location:{state:"MA"}}
//单层合并,city值丢失,后边的location覆盖了前边的location
//更深入的 .extend()
jQuery.extend(true,
{name: "John",location: {city: "Boston"}},
{last: "Resig",location:{state: "MA"}}
);
//结果
// => {name:"John",last: "Resig",location:{city:"Boston",state:"MA"}}
//city与state合并为新的对象
3.可以给jQuery添加静态方法
$.extend({
add: function (a,b){
return a + b;
},
minus: function(a,b){
return a - b;
},
multiply: function(a,b){
return a*b;
},
divide:function(a,b){
return Math.floor(a/b);
}
});
var sum = $.add(3,5) + $.minus(3,5) + $.multiply(3,5)+ $.divide($options,7);
console.log(sum); jquery为开发插件提供了两个方法,分别是:
jQuery.fn.extend(object);
jQuery.extend(object);
jQuery.fn.extend(object);给jQuery对象添加方法。
jQuery.extend(object);为扩展jQuery类本身.为类添加新的方法。
注意用法上的区别
jQuery.fn.myPlugin的用法为 $('.ye').myPlugin
jQuery.extend的用法为 $.add(3,5);
jQuery.fn.myPlugin = function(options) {
$options = $.extend({
html: "no messages",
css : {
"color":"red",
"font-size": "14px"
}},
options);
return $(this).css({
"color": $options.css.color
}).html($options.html);
}
$('.ye').myPlugin({
html: "so esay,yes?",
css: {"color":"green","font-size":"20px"}
});
1.合并多个对象
这是使用的就是$.extend( )的嵌套多个对象的功能。
所谓嵌套多个对象,有点类似于数组的合并的操作。但是这里是对象。举例说明:
用法 jQuery.extend(obj1,obj2,obj3...)
var css1 = {size:"10px",style: "oblique"};
var css2 = {size:"12px",style:"oplique",weight:"bolder"};
jQuery.extend(css1,css2);
//结果:css1的size属性被覆盖,而且继承了css2的weight属性
//css1 = {size: "12px",style: “oblique” ,weight: "bolder"}
2.深度嵌套对象
jQuery.extend(
{name: "John",location: {city: "Boston"}},
{last: "Resig",location:{state: "MA"}}
);
//结果
// => {name:"John",last: "Resig",location:{state:"MA"}}
//单层合并,city值丢失,后边的location覆盖了前边的location
//更深入的 .extend()
jQuery.extend(true,
{name: "John",location: {city: "Boston"}},
{last: "Resig",location:{state: "MA"}}
);
//结果
// => {name:"John",last: "Resig",location:{city:"Boston",state:"MA"}}
//city与state合并为新的对象
3.可以给jQuery添加静态方法
$.extend({
add: function (a,b){
return a + b;
},
minus: function(a,b){
return a - b;
},
multiply: function(a,b){
return a*b;
},
divide:function(a,b){
return Math.floor(a/b);
}
});
var sum = $.add(3,5) + $.minus(3,5) + $.multiply(3,5)+ $.divide($options,7);
console.log(sum); 收起阅读 »
求解决方案:软件盘遮挡输入框问题
问题:
界面输入框接收焦点时,虚拟键盘出现挡住输入框。
环境:
1、全屏显示,不显示状态栏
2、Android系统
3、在云打包后没有出现问题,但是离线打包后就出现这个问题。
既然云打包没有问题,而离线打包后出现了这个问题,那么应该是我离线打包的设置有问题,我打包后其它地方没有问题,这个坑咋都过不去!
求高手给指导。
问题:
界面输入框接收焦点时,虚拟键盘出现挡住输入框。
环境:
1、全屏显示,不显示状态栏
2、Android系统
3、在云打包后没有出现问题,但是离线打包后就出现这个问题。
既然云打包没有问题,而离线打包后出现了这个问题,那么应该是我离线打包的设置有问题,我打包后其它地方没有问题,这个坑咋都过不去!
求高手给指导。









