0%

Object的API众多且重要,按照Object原型方法、进行分类

Object原型方法(所有实例共享)

以下方法来自于Object.prototype,参考《红宝书》P56

(1)constructor

用于创建当前对象的函数

(2)hasOwnProperty(propertyName)

用于判断当前对象实例(不是原型)上是否存在给定的属性,要检查的属性名必须是字符串(如obj.hasOwnProperty("name"))或Symbol

1
2
3
4
5
let person = {
name: 'Katrina'
};

console.log(person.hasOwnProperty('name')); // true

(3)isPrototypeOf(object)

用于判断当前对象是否为另一个对象的原型

1
2
3
4
5
6
7
let person = {
name: 'Katrina',
age: 18,
};

let p = Object.create(person);
console.log(person.isPrototypeOf(p)); // true

(4)propertyIsEnumerable(propertyName)

用于判断给定的属性是否可以使用for-in 语句枚举,属性名必须是字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let person = {};

Object.defineProperty(person, 'name', {
enumerable:true,
value: 'Katrina',
});

Object.defineProperty(person, 'age', {
enumerable:false,
value: 18,
});

console.log(person.propertyIsEnumerable('name')); // true
console.log(person.propertyIsEnumerable('age')); // false

(5)toLocaleString()

返回对象的字符串表示,该字符串反映对象所在的本地化执行环境

1
2
3
4
5
6
let person = {
name: 'Katrina',
age: 18,
};

person.toLocaleString(); // '[object Object]'

(6)toString()

返回对象的字符串表示

1
2
3
4
5
6
let person = {
name: 'Katrina',
age: 18,
};

person.toString(); // '[object Object]'

(7) valueOf()

返回对象对应的字符串、数值或布尔值表示,通常与toString()的返回值相同

1
2
3
4
5
6
let person = {
name: 'Katrina',
age: 18,
};

console.log(person.valueOf()); // {name: Katrina, age: 18}

设置原型方法

(1)Object.create() 【ES5】

Object.create()用于创建一个新对象,使用现有的对象来作为新创建对象的原型。

语法如下:

1
2
Object.create(proto);
Object.create(proto, propertiesObject);

参数说明:

proto:新建对象的原型对象

propertiesObject(可选):传入对象的可枚举属性将为新创建的对象添加指定的属性值和对应的属性描述符,这些属性对应于Object.defineProperties()的第二个参数

返回值:

一个新对象,带着指定的原型对象及其属性

1
2
3
4
5
6
7
let person = {
name: 'Katrina',
age: 18,
};

let p = Object.create(person);
console.log(person.isPrototypeOf(p)); // true

(2)Object.setPrototypeOf(obj, prototype) 【ES6】

Object.setPrototypeOf()方法设置一个指定的对象的原型 ( 即,内部 [[Prototype]] 属性)到另一个对象或 null

语法如下

1
Object.setPrototypeOf(obj, prototype)

参数

  • obj

    要设置其原型的对象。

  • prototype

    该对象的新原型 (一个对象 或 null)

警告:由于现代 JavaScript 引擎优化属性访问所带来的特性的关系,更改对象的 [[Prototype]]各个浏览器和 JavaScript 引擎上都是一个很慢的操作。其在更改继承的性能上的影响是微妙而又广泛的,这不仅仅限于 obj.__proto__ = ... 语句上的时间花费,而且可能会延伸到任何代码,那些可以访问任何 [[Prototype]] 已被更改的对象的代码。如果你关心性能,你应该避免设置一个对象的 [[Prototype]]。相反,你应该使用 Object.create() 来创建带有你想要的 [[Prototype]] 的新对象。

Object属性方法

自觉补课:Object属性类型

(1)定义属性:Object.defineProperty() 【ES5】 && Object.defineProperties() 【ES5】

两者的区别在于前者单个定义,后者多个定义

1
2
3
4
5
6
7
8
9
10
Object.defineProperty(obj, prop, descriptor);

- obj: 要定义属性的对象
- prop: 要定义或修改的属性的名称或Symbol
- descriptor: 要定义或修改的属性描述符


Object.defineProperties(obj,props);
- obj: 要定义属性的对象
- props: 要定义其可枚举属性或者修改的属性描述符的对象

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Object.defineProperty(obj, prop, descriptor);
let person = {};

Object.defineProperty(person, 'name', {
writable: false,
value: 'Katrina',
});

// Object.defineProperties(obj,props);
let person = {};

Object.defineProperties(person, {
name: {
writable: false,
value: 'Katrina',
},

age: {
enumerable: false,
value: 18,
}
})

(2)获取属性的特性:Object.getOwnPropertyDescriptor() 【ES5】 && Object.getOwnPropertyDescriptors() 【ES8】

可以取得指定属性的属性描述符

1
2
3
4
5
6
7
Object.getOwnPropertyDescriptor(obj, prop);

- obj:目标对象
- prop:目标对象内的属性

Object.getOwnPropertyDescriptors(obj)
- obj:目标对象

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let person = {};

Object.defineProperties(person, {
name: {
writable: false,
value: 'Katrina',
},

age: {
enumerable: false,
value: 18,
}
});

let descriptor = Object.getOwnPropertyDescriptor(person, 'name');
console.log(descriptor); // {value: 'Katrina', writable: false, enumerable: false, configurable: false}
console.log(descriptor.writable); // false
console.log(descriptor.enumerable); // false

(3)获取属性名: Object.getOwnPropertyNames() 【ES5】 && Object.getOwnPropertySymbols() 【ES6】

Object.getOwnPropertyNames()返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括 Symbol 值作为名称的属性)组成的数组

Object.getOwnPropertySymbols()返回一个给定对象自身的所有 Symbol 属性的数组

返回值都是数组,注意限定条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Object.getOwnPropertyNames()
var arr = ["a", "b", "c"];
console.log(Object.getOwnPropertyNames(arr).sort()); // ["0", "1", "2", "length"]

// Object.getOwnPropertySymbols()
var obj = {};
var a = Symbol("a");
var b = Symbol.for("b");

obj[a] = "localSymbol";
obj[b] = "globalSymbol";

var objectSymbols = Object.getOwnPropertySymbols(obj);

console.log(objectSymbols.length); // 2
console.log(objectSymbols) // [Symbol(a), Symbol(b)]
console.log(objectSymbols[0]) // Symbol(a)

(4)对象不可扩展: Object.preventExtensions() && Object.seal() && Object.freeze() 【ES5】ALL

Object.preventExtensions(obj)方法让一个对象变的不可扩展,也就是永远不能再添加新的属性,原有的属性可以修改和删除 【阻止】

Object.seal(obj)方法封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置,当前属性的值只要原来是可写的就可以改变,即不允许删除和添加,能不能修改根据writable属性决定 【封闭】

Object.freeze(obj)方法可以冻结一个对象,一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值,啥也干不了呗~ 【冻结】

【根据中文含义,锁定深度是层层递进的~】

举例:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// Object.preventExtensions(obj)
var obj = {};
obj.a = 1;
Object.defineProperty(obj, 'c', {value: 3, configurable: true})
Object.defineProperty(obj, 'd', {value: 4, configurable: true, writable: true})
var obj2 = Object.preventExtensions(obj);
obj === obj2; // true
obj.a = 'a';
obj.b = 2;
obj.c = 'c';
obj.d = 'd'
console.log(obj.a, obj.b, obj.c, obj.d); // a, undefined, 3, d

delete obj.a
console.log(obj.a, obj.b, obj.c, obj.d); // undefined, undefined, 3, d

Object.defineProperty(obj, 'c', {writable: true});
obj.c = 'c';
Object.defineProperty(obj, 'd', {writable: false});
obj.d = 4;
console.log(obj.a, obj.b, obj.c, obj.d); // undefined, undefined, c, d

// Object.seal()
var obj = {};
obj.a = 1;
Object.defineProperty(obj, 'c', {value: 3, configurable: true})
Object.defineProperty(obj, 'd', {value: 4, configurable: true, writable: true})
var obj2 = Object.seal(obj);
obj === obj2; // true
obj.a = 'a';
obj.b = 2;
obj.c = 'c';
obj.d = 'd'
console.log(obj.a, obj.b, obj.c, obj.d); // a, undefined, 3, d

delete obj.a
console.log(obj.a, obj.b, obj.c, obj.d); // a, undefined, 3, d

Object.defineProperty(obj, 'c', {writable: true}); // TypeError
Object.defineProperty(obj, 'd', {writable: false}); // TypeError

// Object.freeze()
var obj = {};
obj.a = 1;
Object.defineProperty(obj, 'c', {value: 3, configurable: true})
Object.defineProperty(obj, 'd', {value: 4, configurable: true, writable: true})
var obj2 = Object.freeze(obj);
obj === obj2; // true
obj.a = 'a';
obj.b = 2;
obj.c = 'c';
obj.d = 'd'
console.log(obj.a, obj.b, obj.c, obj.d); // a, undefined, 3, 4
delete obj.a
console.log(obj.a, obj.b, obj.c, obj.d); // a, undefined, 3, 4


Object.defineProperty(obj, 'c', {writable: true}); // TypeError
Object.defineProperty(obj, 'd', {writable: false}); // TypeError

(5)对象是否可扩展 是否被冻结 是否被封锁:Object.isExtensible() Object.isFrozen() Object.isSealed() 【ES5】ALL

Object.isExtensible判断一个对象是否是可扩展的(是否可以添加新的属性)

Object.isFrozen()方法判断一个对象是否被冻结

Object.isSealed()判断一个对象是否被密封

1
2
Object.isExtensible(obj)  // 返回布尔值
Object.isFrozen(obj) // 返回布尔值

默认情况下,对象是可扩展的:即可以为他们添加新的属性。以及它们的 __proto__Deprecated 属性可以被更改。Object.preventExtensionsObject.sealObject.freeze 方法都可以标记一个对象为不可扩展(non-extensible)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/ 新对象默认是可扩展的。
var empty = {};
Object.isExtensible(empty); // === true

// ...可以变的不可扩展。
Object.preventExtensions(empty);
Object.isExtensible(empty); // === false

// 密封对象是不可扩展的。
var sealed = Object.seal({});
Object.isExtensible(sealed); // === false

// 冻结对象也是不可扩展。
var frozen = Object.freeze({});
Object.isExtensible(frozen); // === false

其他

(1)枚举方法: Object.entries() 【ES8】 && Object.keys() 【ES5】 && Object.values() 【ES8】

Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性)

Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致。

Object.values()方法返回一个给定对象自身的所有可枚举属性值的数组,值的顺序与使用for...in循环的顺序相同 ( 区别在于 for-in 循环枚举原型链中的属性 )

返回值都是数组

举例:

1
2
3
4
5
6
7
8
9
10
11
12
// Object.entries()
const obj = { foo: 'bar', baz: 42 };
console.log(Object.entries(obj)); // [ ['foo', 'bar'], ['baz', 42] ]

// Object.keys()
var anObj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.keys(anObj)); // console: ['2', '7', '100']


// Object.values()
var obj = { foo: 'bar', baz: 42 };
console.log(Object.values(obj)); // ['bar', 42]

(2)合并对象:Object.assign() 【ES6】

Object.assign() 方法将所有可枚举Object.propertyIsEnumerable() 返回 true)和自有(Object.hasOwnProperty() 返回 true)属性从一个或多个源对象复制到目标对象,返回修改后的对象

语法如下

1
Object.assign(target, ...sources);

参数

  • target:目标对象,接收源对象属性的对象,也是修改后的返回值

  • sources:源对象,包含将被合并的属性

举例:

1
2
3
4
5
6
7
const p1 = {name: 'Katrina', age: 20};
const p2 = {age: 18};

const person = Object.assign(p1, p2);

console.log(person); // {name: 'Katrina', age: 18};
console.log(p1); // {name: 'Katrina', age: 18};
  • Object.assign()会改变目标对象

  • Object.assign()只复制属性值,是一种浅拷贝

  • Object.assign()会覆盖从后向前覆盖相同属性

(3)比较方法: Object.is() 【ES6】

Object.is(value1, value2)判断两个值是否为同一个值

相较于===(严格相等),Object.is对两种情况做了改变:

  1. Object.is(NaN, NaN) ; // true
  2. Object.is(-0, +0); // true

其余和===一样

补充:手写Object.is

知识补充

对象 | 对象属性的类型

手写Object.is

深浅拷贝

手写原理| Object.create

循环语句 | for-in&&for-of的区别

参考

JavaScript 对象所有API解析

Object.preventExtensions()、Object.seal()、Object.freeze() 的区别

MDN Object

ECMA-262 使用一些内部特性来描述属性的特征。这些特性是由为JavaScript 实现引擎的规范定义的。因此,开发者不能在JavaScript 中直接访问这些特性。为了将某个特性标识为内部特性,规范会用两个中括号把特性的名称括起来,比如[[Enumerable]]。

属性分为两种:数据属性访问属性

数据属性

(1)[[Configurable]]

表示属性是否可以通过delete 删除并重新定义,是否可以修改它的特性,以及是否可以把它改为访问器属性

默认情况下,所有直接定义在对象上的属性的这个特性都是true

注意:一个属性被定义为不可配置(即)configurable=false后,就不能再变回之前的可配置了configurable=true

(2)[[Enumerable]]

表示属性是否可以通过for-in 循环返回(心里默念:for…in用于遍历对象上可枚举属性,包括原型上的可枚举属性,巴拉巴拉~)

默认情况下,所有直接定义在对象上的属性的这个特性都是true

(3)[[Writable]]

表示属性的值是否可以被修改

默认情况下,所有直接定义在对象上的属性的这个特性都是true

(4)[[Value]]

包含属性实际的值

这个特性的默认值为undefined

数据属性总结与应用

  1. 将属性显式添加到对象之后,[[Configurable]][[Enumerable]][[Writable]]都会被设置为true,而[[Value]]特性会被设置为指定的值

  2. Object.defineProperty()修改属性的默认特性

    1
    2
    3
    4
    5
    Object.defineProperty(obj, prop, descriptor);

    - obj: 要定义属性的对象
    - prop: 要定义或修改的属性的名称或Symbol
    - descriptor: 要定义或修改的属性描述符
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    let person = {};

    Object.defineProperty(person, 'name', {
    writable: false,
    value: 'Katrina',
    });

    console.log(person.name); // Katrina
    person.name = 'Jack';
    console.log(person.name); // Katrina

    /*
    因为设置了writable: false,说明name属性的值不可以被修改
    非严格模式,忽略
    严格模式,抛出错误
    */
  3. 一个属性被定义为不可配置(即)configurable=false后,就不能再变回之前的可配置了configurable=true

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    let person = {};

    Object.defineProperty(person, 'name', {
    configurable: false,
    value: 'Katrina',
    });

    // 抛出错误
    Object.defineProperty(person, 'name', {
    configurable: true,
    value: 'Katrina',
    });
  4. 在调用Object.defineProperty()时,configurable、enumerable 和writable 的值如果不指定,则都默认为false

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    let person = {};

    Object.defineProperty(person, 'name', {
    value: 'Katrina',
    });

    console.log(person.name); // Katrina
    person.name = 'Jack';
    console.log(person.name); // Katrina 说明writable: false
    delete person.name;
    console.log(person.name); // Katrina 说明configurable: false

    for (let k in person) {
    console.log(k) // 啥也没有 说明enumerable: false
    };

访问器属性

访问器属性包含一个获取(getter)函数和一个设置(setter)函数

  1. 在读取访问器属性时,会调用获取函数,这个函数的责任就是返回一个有效的值
  2. 在写入访问器属性时,会调用设置函数并传入新值,这个函数必须决定对数据做出什么修改

访问器属性是不能直接定义的,必须使用Object.defineProperty()

[[Configurable]]

表示属性是否可以通过delete 删除并重新定义,是否可以修改它的特性,以及是否可以把它改为数据属性

默认情况下,所有直接定义在对象上的属性的这个特性都是true

[[Enumerable]]

表示属性是否可以通过for-in 循环返回

默认情况下,所有直接定义在对象上的属性的这个特性都是true

[[Get]]

获取函数,在读取属性时调用

默认值为undefined。

[[Set]]

设置函数,在写入属性时调用

默认值为undefined

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let book = {
year_: 2017,
edition: 1,
};

Object.defineProperty(book, 'year', {
get() {
return this.year_;
},

set(newValue) {
if (newValue > 2017) {
this.year_ = newValue;
this.edition += newValue - 2018;
}
},
})

book.year_ = 2018;
console.log(book.edition); //2

属性访问器的典型使用场景:设置一个属性值会导致一些其他变化发生

知识补充

循环语句 | for-in&&for-of的区别

MDN

instanceof 运算符用于检测构造函数的prototype属性是否出现在实例对象的原型链上

语法

1
object instancof constructor

参数

object:某个实例对象

constructor:某个构造函数

返回值

布尔值

手写实现

实现思路:根据原型链的思想,判断实例对象的__proto__是否指向构造函数的原型,一级一级向上判断,直至null

注意:instanceof只能用于判断引用数据类型,不能判断基本数据类型,直接返回false

1
2
3
4
5
6
7
8
9
10
11
12
13
function myInstanceOf(object, constructor) {
if (typeof object !== 'object' || object === null) return false;
let left = object.__proto__;
let right = constructor.prototype;
while (true) {
// 终止条件1:找到原型了
if (left === right) return true;
// 终止条件2:遍历到了尽头也没找到
if (left === null) return false;
// 还没找到:继续找呗!
left = left.__proto__;
};
};

测试

1
2
3
4
5
6
7
8
9
function Person(name, age) {
this.name = name;
this.age = age;
};

let p = new Person('KK', 18);

console.log(myInstanceOf(p, Person)); // true
console.log(myInstanceOf(p, Object)); // true

知识补充

手写Object.create

手写new

MDN

new运算符创建一个用户定义的对象类型的实例或者具有构造函数的内置对象的实例

语法

1
new constructor[([arguments])]

参数

constructor:一个指定对象实例的类型的类或函数

arguments:一个用于被constructor调用的参数列表

手写new

new关键字会进行如下操作

  1. 创建一个空对象
  2. 为空对象添加__proto__,指向构造函数的prototype对象
  3. 将新对象作为this的上下文,并且执行构造函数内部的代码(给新对象添加属性)
  4. 如果该函数没有返回对象,则返回this

对于返回值的说明

  • 如果构造函数有返回值且是对象,则返回这个对象
  • 如果构造函数有返回值且不是对象,则返回创建的空对象
  • 如果构造函数没有返回值,返回创建的空对象

手写new

1
2
3
4
5
6
7
8
function myNew() {
let newObj = {};
let constr = Array.prototype.shift.call([...arguments]);
let proto = constr.prototype;
newObj = Object.create(proto);
let res = constr.apply(newObj, [...arguments]);
return typeof res === 'object' ? res : newObj;
};

测试

1
2
3
4
5
6
7
8
function Person(name, age) {
this.name = name;
this.age = age;
};

let person = myNew(Person, 'Katrina', 18);
console.log(person); // {name: 'Katrina', age: 18}
console.log(person instanceof Person); // true

知识补充

手写instanceof

手写Object.create

MDN 文档

Object.create()方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型。

语法如下:

1
2
Object.create(proto);
Object.create(proto, propertiesObject);

参数说明:

proto:新建对象的原型对象

propertiesObject(可选):传入对象的可枚举属性将为新创建的对象添加指定的属性值和对应的属性描述符,这些属性对应于Object.defineProperties()的第二个参数

返回值:

一个新对象,带着指定的原型对象及其属性

手写Object.create()

实现思路:定义一个空的构造函数,把构造函数的原型指定为传入的对象,利用new构造函数,返回构造函数的实例,达到创建空对象的目的

1
2
3
4
5
Object.prototype.myCreate = function(obj) {
function Fn() {};
Fn.prototype = obj;
return new Fn();
};

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let obj = {
name: 'Katrina',
age: 18,
};

// Object.create()
let p1 = Object.create(obj);
console.log(p1.__proto__ === obj); // true

// Object.myCreate();
let p2 = Object.myCreate(obj);
console.log(p2.__proto__ === obj); // true


// 测试
console.log(p1 instanceof obj.constructor); // true
console.log(p2 instanceof obj.constructor); // true

知识补充

手写new

手写instanceof

原型和原型链是JavaScript中很重要的一块知识点,因为实例、构造函数、原型函数之前关系,有时候会被绕晕,遂计划进行一波总结~

回顾

  1. 只要创建一个函数,就会按照特定的规则为这个函数创建一个prototype属性(指向原型对象)

  2. 默认情况下,所有的原型的对象自动获得一个名为constructor的属性,指回与之关联的构造函数

  3. 原型对象上包含由构造函数的实例共享的属性和方法

  4. 在自定义构造函数时,原型对象默认只会获得constructor属性,其他的所有方法都继承自Object

  5. 每次调用构造函数创建一个新实例,这个实例内部[[prototype]]指针就会被赋值为构造函数的原型对象,一般我们通过__proto__进行访问对象的原型

实例 构造函数 原型 三者之间的关系

根据之前回顾的知识,若设Person为构造函数,Person.prototype即为原型对象,p=new Person()为构造函数创建的实例,不难可以手绘此图~

根据上图,不难理解:实例与构造函数原型之间有直接的联系,而实例与构造函数之间没有直接联系。

1. 原型行为

下面根据一个例子来说明三者之间的关系

(1)构造函数和原型链循环引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 建立构造函数Person
function Person() {};

// 根据回顾1:只要创建一个函数,就会有原型对象
console.log(Person.prototype);
/*
{
constructor: f Person
[[prototype]]: Object
}
*/

// 根据回顾2:默认情况下,所有的原型的对象自动获得一个名为``constructor``的属性,指回与之关联的构造函数
console.log(Person.prototype.constructor === Person); // true

/*
结合回顾1&&2,不难得到构造函数和原型之间的循环引用关系

Person <————> Person.prototype
*/

(2)原型链终止于Object原型对象

1
2
3
4
5
6
7
8
/*
正常的原型链都会终止于Object的原型对象
Object原型的原型是null
这个在图中已经展示了,下面通过代码进行验证
*/
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Person.prototype.__proto__.constructor === Object); // true
console.log(Person.prototype.__proto__.__proto__ === null); // true

(3)实例访问原型对象

1
2
3
4
5
6
7
8
/*
实例通过new构造函数进行创建
根据回顾5:实例可通过__proto__访问到构造函数的原型

*/

let person1 = new Person();
console.log(person1.__proto__ === Person.prototype); // true

(4)原型操作方法

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
27
28
29
30
31
32
33
34
35
36
37
38
/*
instaceof的原理是:检测构造函数的原型是否存在于实例对象的原型链上
*/
console.log(person1 instanceof Person); // true
console.log(person1 instanceof Object); // true

/*
isPrototyeoOf() 用于检测两个对象之间的原型关系
*/
console.log(Person.prototype.isPrototypeOf(person1)); // true

/*
Object.getPrototypeOf() 用于获取对象的原型
*/
console.log(Object.getPrototypeOf(person1) === Person.prototype); // true

/*
Object.setPrototypeOf() 用于获取对象的原型
*/
let person = {
name: 'Katrina',
};

Object.setPrototypeOf(person1, person);
console.log(Object.getPrototypeOf(person1) === person); // true

/*
Object.setPrototype()会严重影响代码性能,
[Mozilla在文档里说,在所有浏览器和JavaScript 引擎中,修改继承关系的影响都是微妙且深远的。这种影响并不仅是执行Object.setPrototypeOf()
语句那么简单,而是会涉及所有访问了那些修改过[[Prototype]]的对象的代码]
所以可以通过Object.create()来创建一个新对象,同时为其指定原型
*/
let person = {
name: 'Katrina',
};

let person2 = Object.create(person);
console.log(Object.getPrototypeOf(person2) === person); // true

上述关系均可抽象为下图:

2. 原型层级

(1)对象属性的查找机制

对象属性的查找机制:在通过对象访问属性时,会按照这个属性的名称开始搜索。搜索开始于对象实例本身。如果在这个实例上发现了给定的名称,则返回该名称对应的值,如果没有找到这个属性,则搜索会沿着指针进入原型对象,然后在原型对象上找到属性后,再返回对应的值。如果直接原型中没有找到,就会去原型的原型进行寻找,直到找到Object.prototype的原型,指向null为止。

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person() {};

Person.prototype.name = 'Katrina';
Person.prototype.age = 18;
Person.prototype.sayName = function() {
console.log(`Hi, my name is ${this.name}.`);
};

let p1 = new Person();
let p2 = new Person();
p1.name = 'y2d';
console.log(p1.name); // y2d
console.log(p2.name); // Katrina

从上面这个例子不难理解对象属性的查找机制,在p1对象中含有name属性,打印结果为y2d,而p2对象中不含name属性,就沿着指针去对原型寻找name属性并打印出Katrina,同时也可以说明如果实例对象有和原型对象相同的属性,实例对象的属性会遮盖住原型对象的属性,会屏蔽对原型同名属性的访问。

(2)方法

  • delete删除属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Person() {};

Person.prototype.name = 'Katrina';
Person.prototype.age = 18;
Person.prototype.sayName = function() {
console.log(`Hi, my name is ${this.name}.`);
};

let p1 = new Person();
let p2 = new Person();
p1.name = 'y2d';
console.log(p1.name); // y2d
console.log(p2.name); // Katrina

delete p1.name;
console.log(p1.name); // Katrina
  • hasOwnProperty() 方法用于确定某个属性是实例的属性还是原型的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Person() {};

Person.prototype.name = 'Katrina';
Person.prototype.age = 18;
Person.prototype.sayName = function() {
console.log(`Hi, my name is ${this.name}.`);
};

let p1 = new Person();

p1.gender = 'female';

console.log(p1.hasOwnProperty('gender')); // true
console.log(p1.hasOwnProperty('name')); // false

hasOwnProperty() 方法可用于改进for…in枚举对象属性时会枚举原型链上的可枚举属性的缺陷:循环语句 | for-in&&for-of的区别

知识补充

手写new

手写instanceof

手写Object.create

参考

随着posts增多,考虑增加一个搜索功能(起初是因为看了NexT主题官方有个搜索功能觉得很酷),于是乎有了想法就立马行动起来!

其实搜索next主题的_config.yml文件,next主题已经帮我们写好了搜索功能相关的配置。

所以打开上图中的github仓库进行相关配置即可。

Step1:安装hexo-generator-searchdb

1
npm install hexo-generator-searchdb

Step2:在site里的_config.xml增加如下配置

1
2
3
4
5
6
# 站点搜索
search:
path: search.xml
field: post
content: true
format: html

Step3:把next主题的_config.xml的配置打开

1
2
3
4
5
6
7
8
9
10
11
local_search:
enable: true # 这里原本是false 改为true即可
# If auto, trigger search by changing input.
# If manual, trigger search by pressing enter key or search button.
trigger: auto
# Show top n results per article, show all results by setting to -1
top_n_per_article: 5
# Unescape html strings to the readable one.
unescape: false
# Preload the search data when the page loads.
preload: false

效果

  • 侧边菜单增加了search

注意:配置好了search功能后侧边菜单会自动更新,不需要去menu那里设置search

  • 可以正常搜索

参考

https://github.com/theme-next/hexo-generator-searchdb

https://blog.csdn.net/weixin_45877759/article/details/107141789

AJAX概述

Ajax = Asynchronous JavaScript + XML

Ajax主要依赖于XMLHttpRequest(XHR)对象,XHR为发送服务器请求和获取响应提供了合理的接口。这个接口可以实现异步从服务器获取额外数据,意味着用户点击不用页面刷新也可以获取数据。通过XHR对象获取数据后,可以使用DOM方法把数据插入网页。

AJAX优点

W3school里面是这么说的

言简意赅就是在说AJAX可以不刷新页面更新数据,AJAX可以实现与服务器的通信

1. 无刷新更新数据

这是AJAX最大的优点:不用刷新整个页面的前提下与服务器通信维护数据,有利于Web应用程序更为迅速地响应用户交互,避免了在网络上发送没有改变的信息,减少用户等待时间,带来了较好的用户体验。

2. 异步与服务器通信

AJAX使用异步方式与服务器通信,不需要打断用户的操作,具有更加迅速的响应能力。优化了Browser和Server之间的沟通,减少不必要的数据传输、时间以及降低网络上的数据流量。

3. 前端和后端负载平衡

AJAX可以把以前一些服务器负担的工作转嫁到客户端,利用客户端闲置的能力来处理,减轻服务器和带宽的负担,节约空间和宽带租用成本。AJAX的原则是”按需获取数据“,可以最大程度的减少冗余请求和响应对服务器造成的负担,提升站点性能。

4. 基于标准化被广泛支持

AJAX基于标准化的并被广泛支持的技术,不需要下载浏览器插件或者小程序,但需要客户允许在JavaScript浏览器上执行,随着AJAX的成熟,一些简化AJAX的使用方法的程序库也相继问世。

5. 界面与页面分离

AJAX是的Web中的界面与应用分离,有利于分工合作、减少非技术任意对页面的修改造成的web应用程序错误、提高效率,更加适用于现在的发布系统。

AJAX缺点

1. AJAX破坏了浏览器机制

因为AJAX是动态更新局部页面的,所以用户无法回到前一个页面状态,因为一般来说,浏览器可以通过back和history的功能回到历史记录中的静态页面,但是在AJAX中这是无法实现的。

2. AJAX的安全问题

AJAX暴露了浏览器与服务器交互的细节。

3. AJAX不能很好地兼容移动设备

一些手持设备比如手机,PAD等,现在还不能很好地支持AJAX。

AJAX工作原理

基于AJAX的介绍和工作原理,AJAX请求可以分为以下几个步骤:

  1. 新建XHR对象, let xhr = new XMLHttpRequest()
  2. 打开链接 open(),传入三个参数:请求类型,Url,请求是否异步(布尔值)
  3. 发送 send(),接收一个参数,作为请求体发出的数据,如果不需要,则必须传null
  4. 当XHR对象当完成第四步(onreadystatechange)数据接收完成,判断http响应状态(status)200-300之间或者304(缓存)执行回调函数

1. 请求类型说明

计网 | HTTP请求方法

2. 状态码说明

计网 | HTTP状态码

所以,一般来说,HTTP状态码为2xx表示成功,或者是HTTP状态码为304,表示资源未修改过,使用浏览器的缓存中拿取的,也意味着响应有效。

3. readyState说明

  • 0:未初始化(Uninitialized)。尚未调用open()方法。
  • 1:已发送(open)。已调用open()方法,但尚未调用send()方法。
  • 2:已发送(Sent)。已调用send()方法,尚未收到响应。
  • 3:接受中(Receiving)。已经收到部分响应。
  • 4:完成(Complete)。已经收到所有响应,可以使用了。

每次readyState的值发生改变的时候,都会触发readystatechange

4. xhr对象属性说明

收到响应后,xhr对象的以下属性会被填充上数据

  • responseText:作为响应体返回的文本
  • responseXML:如果响应的内容类型是text/xml 或者 application/xml,那就是包含响应数据的XML DOM文件
  • status:响应的HTTP状态
  • statusText:响应的HTTP状态描述

AJAX代码实现

1. AJAX手写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Step1: 新建XHR对象
let xhr = new XMLHttpRequest();

// Step4: 监听
xhr.onreadstatechange = function() {
if (readyState === 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
console.log(xhr.responseText);
} else {
alert('Request was unsucessful!' + xhr.status);
}
}
};

// Step2: 建立连接
xhr.open('GET', url, false);

// Step3: 发送
xhr.send(null);

2. Promise封装AJAX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function myAjax(method='GET', url, isAsync=false, data=null) {
let xhr = new XMLHttpRequest();

return new Promise((resolve, reject) => {
xhr.onreadstatechange = function() {
if (readyState !== 4) return;
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
resolve(xhr.responseText); // 响应体返回的文本
} else {
reject(xhr.statusText); // 响应的HTTP状态描述
};
};
xhr.open(method, url, isAsync);
xhr.send(data);
});
}

参考

https://www.w3schools.com/xml/ajax_intro.asp

https://juejin.cn/post/6844903713102888973

https://segmentfault.com/a/1190000039416782

逗号操作符常用场景:

场景1:在一条语句中执行多个操作

1
let num1 = 1, num2 = 2, num3 = 3;

场景2: 辅助赋值

在赋值的时候使用逗号操作符分隔值,最终会返回表达式中最后一个值

1
let num = (5,1,4,8,0);   // num的值为0

思考题

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
27
28
29
30
31
32
for (let i = 0, j = 0; i < 10, j < 6; i++, j++) {
let k = i + j;
console.log(i, j, k)
};

/*
i j k
0 0 0
1 1 2
2 2 4
3 3 6
4 4 8
5 5 10
*/

for (let i = 0, j = 0; i < 6, j < 10; i++, j++) {
let k = i + j;
console.log(i, j, k)
};

/*
i j k
0 0 0
1 1 2
2 2 4
3 3 6
4 4 8
5 5 10
6 6 12
7 7 14
9 9 18
*/

为什么会出现这样的情况?

当有多个条件之间用;隔开时,会返回最右侧的条件

如果需要两个要求同时满足,应该把;改为&&

数据类型

  • 基本数据类型:简单的数据,保存基本数据类型的变量是按进行访问的,因此直接保存在栈内存中
  • 引用数据类型:保存在堆内存中,在栈内存中保存了一个堆内存中实际对象的引用,引用数据类型的变量是按照引用进行访问的

复制值

  • 基本数据类型:在通过变量把一个基本数据赋值到另一个变量时,基本数据会被复制到新变量的位置
1
2
let num1 = 5;
let num2 = num1;

从上图可知,num1和num2是独立使用的,互不干扰

  • 引用数据类型:在把引用值从一个变量赋给另一个变量时,存储在变量中的值也会被复制到新变量所在的位置。区别在于,这里复制的值实际上是一个指针,它指向存储在堆内存中的对象,操作完成后,两个变量实际上指向同一个对象,因此一个对象上面的变化会在另一个对象上反映出来

深浅拷贝

深浅拷贝都是针对于引用类型的,因为引用类型是存放在堆内存中的,在栈地址中有一个或者多个地址来指向堆内存的某一数据

浅拷贝

创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝

如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象

浅拷贝是指被复制对象的所有变量都与原来的对象含有相同的值,而所有的对其他对象的引用仍然指向原来的对象

浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象,如果你修改了“副本”的值,那么原来的对象也会被修改

浅拷贝只拷贝一层,深层次的引用类型则共享内存地址

深拷贝

将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象

深拷贝是一个整个独立对象的拷贝,深拷贝会拷贝所有的属性,并且拷贝属性指向的动态分配的内存

深拷贝把要复制的对象所引用的对象都复制了一遍,如果你修改了“副本”的值,那么原来的对象不会被修改,两者是相互独立的

实现浅拷贝的方式

  • Object.assign
  • Array.protptype.slice
  • Array.protptype.concat
  • 使用拓展运算符
  • 手写浅拷贝

Object.assign

补课:Object.assign

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let person = {
name: 'Katrina',
age: 18,
hobbies: ['run', 'swim', 'play football'],
love: function() {
console.log('She is a cool girl!')
},
};

let person1 = Object.assign({}, person);

// 检验深拷贝or浅拷贝
person1.hobbies[2] = 'play tennies';
console.log(person1.hobbies); // ['run', 'swim', 'play tennies']
console.log(person.hobbies); // ['run', 'swim', 'play tennies']

Array.prototype.slice()

补课:slice

1
2
3
4
5
6
7
const colors = ['red', 'yellow', ['green', 'black']];
const colors1 = colors.slice(0);

// 检验深拷贝or浅拷贝
colors1[2][1] = 'pink';
console.log(colors); // ['red', 'yellow', ['green', 'pink']]
console.log(colors1); // ['red', 'yellow', ['green', 'pink']]

Array.prototype.concat()

补课:concat

1
2
3
4
5
6
7
const colors = ['red', 'yellow', ['green', 'black']];
const colors1 = colors.concat();

// 检验深拷贝or浅拷贝
colors1[2][1] = 'pink';
console.log(colors); // ['red', 'yellow', ['green', 'pink']]
console.log(colors1); // ['red', 'yellow', ['green', 'pink']]

拓展运算符

1
2
3
4
5
6
7
const colors = ['red', 'yellow', ['green', 'black']];
const colors1 = [...colors];

// 检验深拷贝or浅拷贝
colors1[2][1] = 'pink';
console.log(colors); // ['red', 'yellow', ['green', 'pink']]
console.log(colors1); // ['red', 'yellow', ['green', 'pink']]

手写浅拷贝

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
function shallowClone(target) {
let res, targetType = Object.prototype.toString.call(target).slice(8,-1).toLowerCase();

if (target === 'object') {
res = {};
} else if (target === 'array') {
res = [];
} else {
return target;
}

for (let key in target) {
res[key] = target[key];
};

return res;
};

// test1
const colors = ['red', 'yellow', ['green', 'black']];
const colors1 = shallowClone(colors);
// 检验深拷贝or浅拷贝
colors1[2][1] = 'pink';
console.log(colors); // ['red', 'yellow', ['green', 'pink']]
console.log(colors1); // ['red', 'yellow', ['green', 'pink']]


// test2
let person = {
name: 'Katrina',
age: 18,
hobbies: ['run', 'swim', 'play football'],
love: function() {
console.log('She is a cool girl!')
},
};

let person1 = shallowClone(person);

// 检验深拷贝or浅拷贝
person1.hobbies[2] = 'play tennies';
console.log(person1.hobbies); // ['run', 'swim', 'play tennies']
console.log(person.hobbies); // ['run', 'swim', 'play tennies']

实现深拷贝的方式

  • _.cloneDeep()
  • JQuery.extend()
  • JSON.stringify()
  • 手写深拷贝

_.cloneDeep()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const _ = require('loash');
let person = {
name: 'Katrina',
age: 18,
hobbies: ['run', 'swim', 'play football'],
love: function() {
console.log('She is a cool girl!')
},
};
let person1 = _.cloneDeep(person);

// 检验深拷贝or浅拷贝
person1.hobbies[2] = 'play tennies';
console.log(person1.hobbies); // ['run', 'swim', 'play tennies']
console.log(person.hobbies); // ['run', 'swim', 'play football']

JQuery.extend()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const $ = require('jquery');
let person = {
name: 'Katrina',
age: 18,
hobbies: ['run', 'swim', 'play football'],
love: function() {
console.log('She is a cool girl!')
},
};
let person1 = $.extend(person);

// 检验深拷贝or浅拷贝
person1.hobbies[2] = 'play tennies';
console.log(person1.hobbies); // ['run', 'swim', 'play tennies']
console.log(person.hobbies); // ['run', 'swim', 'play football']

JSON.stringify()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let person = {
name: 'Katrina',
age: 18,
hobbies: ['run', 'swim', 'play football'],
love: function() {
console.log('She is a cool girl!')
},
};

let person1 = JSON.parse(JSON.stringify(person));

// 检验深拷贝or浅拷贝
person1.hobbies[2] = 'play tennies';
console.log(person1.hobbies); // ['run', 'swim', 'play tennies']
console.log(person.hobbies); // ['run', 'swim', 'play football']

// 打印person1发现...
console.log(person1);
console.log(person);

// 如下图,function不见了...这就是JSON.stringify()方法的弊端

这种方法会存在弊端:会忽略undefinedfunctionsymbol的存在

1
2
3
4
5
6
7
8
9
10
11
12
const test = {
a: 'hello',
b: undefined,
c: Symbol('I am fine'),
d: function() {
console.log('and you?');
},
};

const test1 = JSON.parse(JSON.stringify(test));

console.log(test1); // {a: 'hello'}

手写深拷贝

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
function deepClone(target) {
let res, targetType = Object.prototype.toString.call(target).slice(8, -1).toLowerCase();

if (targetType === 'object') {
res = {};
} else if (targetType === 'array') {
res = [];
} else {
return target;
};


for (let key in target) {
res[key] = deepClone(target[key]);
};

return res;
};

// test1
const colors = ['red', 'yellow', ['green', 'black']];
const colors1 = deepClone(colors);
// 检验深拷贝or浅拷贝
colors1[2][1] = 'pink';
console.log(colors); // ['red', 'yellow', ['green', 'black']]
console.log(colors1); // ['red', 'yellow', ['green', 'pink']]


// test2
let person = {
name: 'Katrina',
age: 18,
hobbies: ['run', 'swim', 'play football'],
love: function() {
console.log('She is a cool girl!')
},
};

let person1 = deepClone(person);

// 检验深拷贝or浅拷贝
person1.hobbies[2] = 'play tennies';
console.log(person1.hobbies); // ['run', 'swim', 'play tennies']
console.log(person.hobbies); // ['run', 'swim', 'play football']

拓展:赋值和拷贝

拷贝区别于赋值最重要的一点是:拷贝会在堆上创建一个新的对象

示意图如下图所示:

和原对象是否指向同一地址 第一层数据为基本数据类型 原数据中包含子对象
赋值 改变会使原数据一同改变 改变会使原数据一同改变
浅拷贝 改变不会使原数据一同改变 改变会使原数据一同改变
深拷贝 改变不会使原数据一同改变 改变不会使原数据一同改变

参考

深入理解JS深浅拷贝

赋值和拷贝