jsRe

文章目录

基础

判断数据类型 typeof

判断对象类型 instanceof

用 in 操作符来检查属性是否存在 key(属性名) in Object

删除对象的属性 delete delete Object['属性名']

拷贝

浅拷贝

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
// 这是浅拷贝,拷贝返回的对象与原对象使用同一引用类型值地址
function clone() {
var newObj = {}
for(var i in obj) {
newObj[i] = obj[i]
}
return newObj
}
// Object.assign 拷贝对象 Object.assign(target,{}(源对象1),obj2(源对象2))
// 只能复制一层对象,如果对象的属性值对应了其他引用类型,还是一样会出现问题,实际上就是个浅拷贝。如
var obj1 = {
a: 1,
b: 2,
c: ['a', 'b']
}
var obj2 = Object.assign({}, obj1)
obj2.c[1] = 5
obj2.c // ['a', 5]
obj1.c // ['a', 5]
// 实际上这种方法常用来赋值对象的默认参数,如
function aa(obj) {
obj = Object.assign({
name: 'dl',
age: 11
}, obj)
// 默认对象 .name = 'dl', .age = 11
// 如果 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
// 乞丐版深拷贝 JSON 方法
var obj1 = {
a: [1],
b: 2,
c: ['a', 'b']
}
var objString = JSON.stringify(obj1)
var obj2 = JSON.parse(objString)
obj2.a = 5
obj1.a // [1]
obj2.a // 5
// 引用没有问题,修改 obj2 ,不会对 obj1 造成任何影响,但它无法拷贝 undefined,function,RegExp 等类型的,所以叫做乞丐版

// 递归版
function deepClone(obj) {
if(obj === null) return null
if(obj instanceof RegExp) return new RegExp(obj)
if(obj instanceof Date) return new Date(obj)
if(type obj != 'object') {
return obj
}
let newObj = new obj.constructor()
for (let key in obj) {
newObj[key] = deepClone(obj[key])
}
return newObj
}

上下文对象创建和初始化

  • 全局:
    • 在全局代码执行前最先创建一个全局上下文对象(window)
    • 收集一些全局变量/函数, 并初始化
    • 将这些变量设置为上下文对象的属性
  • 函数:
    • 在调用函数时, 在执行函数体之前先创建一个函数上下文对象
    • 收集一些局部变量,/函数 并初始化
    • 将这些变量设置为上下文对象的属性
  • 声明提升
    • 变量提升: 在变量定义语句之前, 就可以访问到这个变量(undefined)
    • 函数提升: 在函数定义语句之前, 就可以执行该函数
    • 如果一个变量和函数同名,函数声明优先于变量声明
    • 函数中形参的赋值在函数声明之前

练习:

1
2
3
4
5
6
7
8
var foo = 1;
function bar() {
if (!foo) {
var foo = 10;
}
alert(foo); // ?
}
bar();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var a = 1;
function b() {
a = 10;
return;
function a() {}
}
b();
alert(a); // ?

function b(t) {
function t() {

}
console.log(t) // function t() {}
// 由于 函数中形参的赋值在函数声明之前, 10被覆盖掉
}
b(10)

严格模式:

‘use strict’

为了规范 js 的语言类型,推出了严格模式,严格模式下未经声明的值不可使用。

描述对象属性的特性的属性

到目前为止,属性对我们来说是一个简单的“键-值”对。但对象属性实际上是更复杂可变的东西。

两种描述属性:数据属性和访问器属性

1. 数据属性

数据属性包含一个数据值的位置。在这个位置可以读取和写入值。数据属性有 4 个描述其行为的特性

  • [[Configurable]] :是否可配置标志,表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。

    有时它会预设在内置对象和属性中。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 true

    当它的值为false的时候,一个不可配置的属性不能被 defineProperty 删除或修改。

  • [[Enumerable]] :表示能否通过 for-in 循环返回属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 true 枚举/穷举/遍历

  • [[Writable]] :表示能否修改属性的值。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 true

  • [[Value]] :包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。这个特性的默认值为 undefined

像上面的两种方法那样直接在对象上定义属性和方法,它们的 [[Configurable]] 、 [[Enumerable]] 和 [[Writable]] 特性都被设置为 true ,而 [[Value]] 特性被设置为指定的值。可以通过 Object.defineProperty() 来定义

属性描述符可以在各个属性的级别上工作。还有一些限制访问整个对象的方法:

还有对他们的测试:

  • Object.isExtensible(obj)

    如果添加属性被禁止,则返回 false,否则返回 true

  • Object.isSealed(obj)

    如果禁止添加/删除属性,则返回 true,并且所有现有属性都具有 configurable: false

  • Object.isFrozen(obj)

    如果禁止添加/删除/更改属性,并且所有当前属性都是 configurable: false, writable: false,则返回 true

这些方法在实践中很少使用。

2. 访问器属性

访问器属性不包含数据值;它包含一对儿 getter 和 setter 函数(不过,这两个函数都不是必需的)。在读取访问器属性时,会调用 getter 函数,这个函数负责返回有效的值;在写入访问器属性时,会调用 setter 函数并传入新值,这个函数负责决定如何处理数据。

  • [[Get]] :在读取属性时调用的函数。默认值为 undefined
  • [[Set]] :在写入属性时调用的函数。默认值为 undefined

在对象字面量中,它们用 getset 表示:

1
2
3
4
5
6
7
8
9
10
11
12
// 从外表看,访问器属性看起来像一个普通的属性。这是访问器属性的设计思想。我们不以函数的方式调用 user.fullName,我们通常读取它:getter 在幕后运行。
// 这样就创建了一个“虚拟”属性。它是可读写的,但实际上并不存在。

let obj = {
get propName() {
// getter, the code executed on getting obj.propName
},

set propName(value) {
// setter, the code executed on setting obj.propName = value
}
};

访问器属性的描述符

访问器属性的描述符与数据属性相比是不同的。

对于访问器属性,没有 valuewritable,但是有 getset 函数。

所以访问器描述符有:

  • get —— 一个没有参数的函数,在读取属性时工作,
  • set —— 带有一个参数的函数,当属性被设置时调用,
  • enumerable —— 与数据属性相同,
  • configurable —— 与数据属性相同。

例如,要使用 defineProperty 创建 fullName 的访问器,我们可以使用 getset 来传递描述符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let user = {
name: "John",
surname: "Smith"
};

Object.defineProperty(user, 'fullName', {
get() {
return `${this.name} ${this.surname}`;
},

set(value) {
[this.name, this.surname] = value.split(" ");
}
});

alert(user.fullName); // John Smith

for(let key in user) alert(key); // name, surname

受保护的属性通常以下划线 _ 作为前缀,这不是在语言层面强制实施的,但是有一个在程序员之间人尽皆知的惯例是不应该从外部访问这些属性和方法。

注意: 属性可以是“数据属性”或“访问器属性”,但不能同时属于两者。

1
2
3
4
5
6
7
// 同时存在 get函数和 value,会报错
Object.defineProperty({}, 'prop', {
get() {
return 1
},
value: 2
});

3. defineProperty

要修改属性默认的特性,必须使用 Object.defineProperty() 方法。

语法:

1
2
3
Object.defineProperty(obj, propertyName, descriptor)
// obj,propertyName 要处理的对象和属性。
// descriptor 要应用的属性描述符。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var person = {};
Object.defineProperty(person, "name", {
enumerable: true, // 是否可枚举,默认值为 true; 如果为false,打印这个对象,是看不到name这个属性的
writable:true, // 是否可写,默认值 true; 如果为false的话,给name赋值,不会生效
configurable:true, // 是否可配置(是否可删除),默认值 true
// 如果为true,delete obj.name,再打印person,则显示{}
// 如果为false,delete obj.name,再打印person,则显示{name:Nicholas}
value:'Nicholas', // name对应的值
});
alert(person.name);
delete person.name;
alert(person.name);

Object.defineProperty(person,"name",{
get: function (){
// 当读取person.name时回调用此函数,拿取返回值
return 'xxx';
},
set: function (newValue){
// 更改person.name的值时,回调用此函数,默认形参为我们设置的新值
person.name = newValue;
}
})

注意:

  1. 使属性不可配置是一条单行道。我们不能把它改回去,因为 defineProperty 不适用于不可配置的属性。

  2. 设置不可用的属性时,只在使用严格模式时才会出现错误。在非严格模式下,写入只读属性等时不会发生错误。但操作仍然不会成功。非严格模式下违反标志的行为只是默默地被忽略。

双向绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<input type="text" v-module="name">

<script type="text/javascript">
var obj = {};
var ipt = document.querySelector("input[v-module]");

ipt.oninput = function() {
obj.name = ipt.value;
}

Object.defineProperty(obj,'name',{
get(){
return ipt.value
},
set(v){
ipt.value = v;
}
})
</script>

4. defineProperties

Object.defineProperties() 方法用来一次定义多个属性,这个方法接受两个对象参数: 添加或修改其属性的对象,与第一个对象中要添加或修改的属性一一对应的对象

语法:

1
2
3
4
5
Object.defineProperties(obj, {
prop1: descriptor1,
prop2: descriptor2
// ...
});

例如:

1
2
3
4
5
Object.defineProperties(user, {
name: { value: "John", writable: false },
surname: { value: "Smith", writable: false },
// ...
});

5. getOwnPropertyNames

Object.getOwnPropertyNames(obj) 可以得到 obj 所有的属性,无论它是否可枚举。

1
2
3
4
5
6
7
8
9
var obj = {
name: "李白",
age: 24
}
Object.defineProperty(obj, "tel", {
value: "123456"
})

Object.getOwnPropertyNames(obj) //  ["name", "age", "tel"]

6. Object.keys

获取对象所有可枚举的属性

1
2
3
4
5
6
7
8
var obj = {
name: "李白",
age: 24
}
Object.defineProperty(obj, "tel", {
value: "123456"
})
console.log(Object.keys(obj)); // ["name", "age"]

作业:

给任意 input 元素添加 v-module 属性,在 data 中添加相应属性,并实现双向数据绑定,生成的属性不可枚举。

1
2
3
4
5
6
7
8
9
<!-- name改为"李白",data中添加name属性,值为"李白" -->
<input type="text" placeholder="请输入姓名" value="" v-module="name">
<input type="text" placeholder="请输入年龄" value="" v-module="age">
<input type="text" placeholder="请输入年龄" value="">


<script>
var data = { };
</script>

题外

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var name = 'tt'
var obj = {
fn: function () {
console.log(this.name)
},
name: 'dl'
}
var wf = obj.fn
// 实际上是把 function () { console.log(this.name) } 这个函数,给了 wf, wf 在 window 声明了这个函数。
// 是把函数这个引用数据类型的地址给了 wf !
// 相当于 window 对象上多了一条 属性
// {属性... , wf: function () { console.log(this.name) }}
// 所以 wf() 的 this 指向 window ,返回的是 window.name

wf() // 'tt'
setTimeout(function () {
obj.fn() // 'dl' 由于仍然调用的是 obj 中的 fn, 所以 this 指向 obj ,所以打印 obj.name
},0)

// setTimeout 的第一个参数是个回调函数,其实和上面的 wf 一样,只是拿到了 obj.fn 的地址,然后时间到了之后用一次。
// 相当于 var callback = obj.fn
// setTimeout(callback, 0)
setTimeout(obj.fn, 0) // 'tt'
函数的作用域在定义的时候就已经确定了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function af() {
var c = 0
return function () { // 闭包
console.log(c)
c++
}
}
var b = af()
var c = af()
b() // 0 实际上 c 此时是1,因为console.log 在 c++ 之前,下同
b() // 1
b() // 2
c() // 0 由于 c = af(),相当于重新执行了一遍 af(),所以 c = 0
b() // 3 b 和 c 实际是两个不同的函数,只是看起来一样而已,它们的内存地址并不一样,每次执行 af(),都在内存中开辟了新的空间,返回了一个新的函数, 它们各自控制自己父级作用域内的 c
分享到:

评论完整模式加载中...如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理