zaizizaizai

a dream of a child


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

  • 搜索

记一次网站解析

发表于 2018-03-01 | 分类于 实践 | 阅读次数

前言

今天记起要选公选课,同学安利一个教务处app,打开一看发现界面和功能都挺不错的,突然有做教务处微信小程序版本的想法,第一想法就是去找官网api,可转眼一想,哪来的api?原作者说网站解析来的,于是开始第一次抓包过程。

1、去长大官网溜了一圈

首先来到官网看了下

1.1、登录前

教务处登录页面
长大教务处登录前

发送请求中的header
长大教务处登录前header

1.2、登陆后

长大教务处登录后data

1.3、测试

postman测试
postMan发送教务处请求失败

一开始想到把请求数据拿到,再伪造不就行了吗?至于怎么发送、接受数据,后面再说嘛…
结果转眼一想,这还有验证码,难道要把验证码都转发给客户端?我看到的教务处app中用户名和密码都是学号,不科学啊,难道他找到漏洞,把学校教务处数据爬下来了?
我转向解析app

2、解析app

app发送到服务器的数据
app请求

从截图中Request PostData这一栏中可以看到发送的数据,这个username(学号)没加密,password(密码)加密了,由于学号就是用户名和密码,我试着把密码用md5方法编码、URL编/解码…等一系列还有我没见过的编/解码方法,结果都不与password/原密码匹配,我猜应该加入了干扰字符吧,寻找编码方法失败。
但可以看到是发送到http://221.233.24.23/eams/login.action,就去这个网址看看。

3、 教学管理系统溜一圈

3.1、登录前

访问这个地址,有登录界面,这张图片是以错误的用户名密码登录时浏览器发送的数据

以错误的用户名密码登录时浏览器发送的数据
失败登录教学管理系统

username是明文发送,password则加密了,可以看到也是发送了4个参数,这和app发送的请求参数相同,说明那个教务处app就是发送请求到这个教学管理系统的

3.2、登陆后

以正确的用户名密码登录时浏览器发送的数据
成功登录教学管理系统

这里图片无法显示,图片内容就是与上一个图片内容类似,但就是没有post data,这是什么?没见过啊,为什么登陆成功不发送数据,登录错误还可以显示数据

留一个坑,不理解

3.3、测试

postman发送教学管理系统失败

结语

模板引擎jade与ejs的区别与重点

发表于 2018-02-27 | 分类于 node | 阅读次数

一、模板引擎

//TODO

二、jade

1、认识jade

//TODO

2、基础语法

2.1、根据缩进划分层级

2.2、属性用()表示,用逗号分割

*style = []
*class = []

2.3、内容

1
2
3
div xxx
span xxx
a(href="xxx")链接

2.4、渲染

jade.render(‘字符串’);
jdde.renderFile(‘模板文件名’, 参数);

3、高级用法

3.1原样输出

1
2
3
4
5
6
7
8
9
10
11
12
//1.jade
html
head
body
zaizi

//转为对应html
<html>
<head></head>
<body>
<zaizi></zaizi>
</body>

从上面代码可以看出,原意是body的内容为zaizi,但实际转译把zaizi作为自定义标签。如何在标签里原样显示内容呢?如下:

加竖线|,原样输出

1
2
3
4
5
6
7
8
9
10
//1.jade
html
head
body
|zaizi

//转为对应html
<html>
<head></head>
<body>zaizi</body>

此语法可用于在html代码中写js,例子:

1
2
3
4
5
6
7
8
html
head
script
| //js代码
| //js代码
body
|zaizi
|dog

以上情况满足写在html中js的需求,但每一行都需要加|,还是比较麻烦,更简单的方法是:在script后面加一个点.,例子:

在标签后加一个点.表示标签后的内容都原样输出

1
2
3
4
5
html
head
script.
// js代码
body

若要文件结构更清楚,可以尝试引用外部js文件,但会增加http请求数,这里可以采用include,例子:

include引用文件

1
2
3
4
5
html
head
script.
include a.js
body

3.2 使用变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//jade代码
html
head
body
div 我的名字: #{name}

//js代码
const jade = require('jade');

const.log(jade.renderFile('`jade文件地址`', {pretty: true, name: 'zaizi'}));


//编译效果
<html>
<head></head>
<body>
<div>我的名字: zaizi</div>
</body>
</html>

剖析Promise内部结构

发表于 2018-02-25 | 分类于 node | 阅读次数

前言

阅读本文需要对promise有一定了解,可阅读详解promise

Promise标准解读

1.只有一个then方法,没有catch,race,all等方法,甚至没有构造函数

Promise标准中仅指定了Promise对象的`then`方法的行为,其他常见的方法/函数都没有指定,包括`catch`,`race`,`all`等常用方法,甚至也没有指定该乳环构造一个Promise对象,而且`then`也没有一般实现中(Q,$q等)所支持的第三个参数,一般成为onProgress

2.then方法返回一个新的Promise

Promise的`then`方法返回一个新的Promise,而不是返回this,此处下文会有更多解释

1
2
promise2 = promise1.then(alert);
promise2 != promise1 //true

3.不同Promise的实现需要可以相互调用(interoperable)

4.Promise的初始状态为pending,它可以由此状态转换为fulfilled(也叫resolved)或者rejected,一旦状态确定,就不可以再转换为其他状态,状态确定的过程成为settle

5.更具体的标准

一步一步实现一个Promise

一步一步实现一个Promise

构造函数

标准中并没由指定如何构造一个Promise对象,在此以实现Promise的通用方法来构造一个Promise对象,即ES6原生Promise里所使用的方法:

1
2
3
4
5
6
7
//Promise构造函数接收一个executor函数,execuyor函数执行完同步或者异步操作后,调用它的两个参数resolve和reject
var promise = new Promise(function(resolve, reject) {
/*
如果操作成功,调用resolve并传入value
如果操作失败,调用reject并传入reason
*/
});

先实现构造函数的框架:

1
2
3
4
5
6
7
8
9
function Promise(executor) {
var self = this;
self.status = 'pending'; //Promise当前的状态
self.data = undefined; //Promise的值
self.onResolvedCallback = []; //Promise resolve时的回调函数
self.onRejectedCallback = []; //Promise reject时的回调函数

executor(resolve, reject); //执行executor并传入相应的参数
}

上面代码基本实现了Promise构造函数的主体,但还是有两个问题:

1.我们给executor函数传了两个参数:resolve和reject,这两个参数尚未定义

2.executor也有可能出错(throw),此时,Promise应该被其throw出的值reject:

1
2
3
new Promise(function(resolve, reject) {
throw 2;
})

所以我们需要在构造函数里定义resolve和reject这两个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Promise(executor) {
var self = this;
var self = this
self.status = 'pending' // Promise当前的状态
self.data = undefined // Promise的值
self.onResolvedCallback = [] // Promise resolve时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面
self.onRejectedCallback = [] // Promise reject时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面

function resolve(value) {
// TODO
}

function reject(reson) {
// TODO
}

try { //try/catch捕捉错误
executor(resolve, reject); //执行executor
} catch(e) {
reject(e);
}
}

原文

博文置顶测试

发表于 2018-02-25 | 分类于 博客建设 | 阅读次数

使用方法

在文章中添加top值,数值越大文章越靠前,如:

1
2
3
4
5
title: 博文置顶测试
date: 2018-02-25 17:09:16
tags: [测试,博客]
categories: 博客说明
top: 3

文章密码测试

发表于 2018-02-25 | 分类于 博客建设 | 阅读次数
The article has been encrypted, please enter your password to view.
阅读全文 »

科学上网

发表于 2018-02-25 | 分类于 技巧 | 阅读次数

heroku上部署ss

先来占个坑

之前在github上发现了一个在heroku上搭建shadowsocks的项目,自己试着搭建,发现极好用。此时原项目已被作者删除,重新找了一个。
特点:

1、快速,youtube上4k视频无压力
2、无限流量
3、免费!免费!


github项目地址

理解JavaScript--深克隆

发表于 2018-02-25 | 分类于 JavaScript | 阅读次数

八、在JavaScript中如何实现一个深克隆


前言

在要实现一个深克隆之前我们需要了解一下javascript中的基础类型.

javascript基础类型   

JavaScript原始类型:Undefined、Null、Boolean、Number、String、Symbol
JavaScript引用类型:Object


1.浅克隆

  浅克隆之所以被称为浅克隆,是因为对象只会被克隆最外部的一层,至于更深层的对象,则依然是通过引用指向同一块堆内存.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 浅克隆函数
function shallowClone(o) {
const obj = {};
for ( let i in o) {
obj[i] = o[i];
}
return obj;
}
// 被克隆对象
const oldObj = {
a: 1,
b: [ 'e', 'f', 'g' ],
c: { h: { i: 2 } }
};

const newObj = shallowClone(oldObj);
console.log(newObj.c.h, oldObj.c.h); // { i: 2 } { i: 2 }
console.log(oldObj.c.h === newObj.c.h); // true

我们可以看到,很明显虽然oldObj.c.h被克隆了,但是它还与oldObj.c.h相等,这表明他们依然指向同一段堆内存,这就造成了如果对newObj.c.h进行修改,也会影响oldObj.c.h,这就不是一版好的克隆.

1
2
newObj.c.h.i = 'change';
console.log(newObj.c.h, oldObj.c.h); // { i: 'change' } { i: 'change' }

我们改变了newObj.c.h.i的值,oldObj.c.h.i也被改变了,这就是浅克隆的问题所在.

当然有一个新的apiObject.assign()也可以实现浅复制,但是效果跟上面没有差别,所以我们不再细说了.

2.深克隆

2.1 JSON.parse方法

前几年微博上流传着一个传说中最便捷实现深克隆的方法,
JSON对象parse方法可以将JSON字符串反序列化成JS对象,stringify方法可以将JS对象序列化成JSON字符串,这两个方法结合起来就能产生一个便捷的深克隆.

1
const newObj = JSON.parse(JSON.stringify(oldObj));

我们依然用上一节的例子进行测试

1
2
3
4
5
6
7
8
9
10
11
const oldObj = {
a: 1,
b: [ 'e', 'f', 'g' ],
c: { h: { i: 2 } }
};

const newObj = JSON.parse(JSON.stringify(oldObj));
console.log(newObj.c.h, oldObj.c.h); // { i: 2 } { i: 2 }
console.log(oldObj.c.h === newObj.c.h); // false
newObj.c.h.i = 'change';
console.log(newObj.c.h, oldObj.c.h); // { i: 'change' } { i: 2 }

果然,这是一个实现深克隆的好方法,但是这个解决办法是不是太过简单了.

确实,这个方法虽然可以解决绝大部分是使用场景,但是却有很多坑.

1.他无法实现对函数 、RegExp等特殊对象的克隆

2.会抛弃对象的constructor,所有的构造函数会指向Object

3.对象有循环引用,会报错

主要的坑就是以上几点,我们一一测试下.

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
// 构造函数
function person(pname) {
this.name = pname;
}

const Messi = new person('Messi');

// 函数
function say() {
console.log('hi');
};

const oldObj = {
a: say,
b: new Array(1),
c: new RegExp('ab+c', 'i'),
d: Messi
};

const newObj = JSON.parse(JSON.stringify(oldObj));

// 无法复制函数
console.log(newObj.a, oldObj.a); // undefined [Function: say]
// 稀疏数组复制错误
console.log(newObj.b[0], oldObj.b[0]); // null undefined
// 无法复制正则对象
console.log(newObj.c, oldObj.c); // {} /ab+c/i
// 构造函数指向错误
console.log(newObj.d.constructor, oldObj.d.constructor); // [Function: Object] [Function: person]

我们可以看到在对函数、正则对象、稀疏数组等对象克隆时会发生意外,构造函数指向也会发生错误。

1
2
3
4
5
6
const oldObj = {};

oldObj.a = oldObj;

const newObj = JSON.parse(JSON.stringify(oldObj));
console.log(newObj.a, oldObj.a); // TypeError: Converting circular structure to JSON

对象的循环引用会抛出错误.

2.2 构造一个深克隆函数

我们知道要想实现一个靠谱的深克隆方法,上一节提到的序列/反序列是不可能了,而通常教程里提到的方法也是不靠谱的,他们存在的问题跟上一届序列反序列操作中凸显的问题是一致的.

(这个方法也会出现上一节提到的问题)

由于要面对不同的对象(正则、数组、Date等)要采用不同的处理方式,我们需要实现一个对象类型判断函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const isType = (obj, type) => {
if (typeof obj !== 'object') return false;
const typeString = Object.prototype.toString.call(obj);
let flag;
switch (type) {
case 'Array':
flag = typeString === '[object Array]';
break;
case 'Date':
flag = typeString === '[object Date]';
break;
case 'RegExp':
flag = typeString === '[object RegExp]';
break;
default:
flag = false;
}
return flag;
};

这样我们就可以对特殊对象进行类型判断了,从而采用针对性的克隆策略.

1
2
3
const arr = Array.of(3, 4, 5, 2);

console.log(isType(arr, 'Array')); // true

对于正则对象,我们在处理之前要先补充一点新知识.

我们需要通过正则的扩展了解到flags 属性 等等,因此我们需要实现一个提取flags的函数.

1
2
3
4
5
6
7
const getRegExp = re => {
var flags = '';
if (re.global) flags += 'g';
if (re.ignoreCase) flags += 'i';
if (re.multiline) flags += 'm';
return flags;
};

做好了这些准备工作,我们就可以进行深克隆的实现了.

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
/**
* deep clone
* @param {[type]} parent object 需要进行克隆的对象
* @return {[type]} 深克隆后的对象
*/
const clone = parent => {
// 维护两个储存循环引用的数组
const parents = [];
const children = [];

const _clone = parent => {
if (parent === null) return null;
if (typeof parent !== 'object') return parent;

let child, proto;

if (isType(parent, 'Array')) {
// 对数组做特殊处理
child = [];
} else if (isType(parent, 'RegExp')) {
// 对正则对象做特殊处理
child = new RegExp(parent.source, getRegExp(parent));
if (parent.lastIndex) child.lastIndex = parent.lastIndex;
} else if (isType(parent, 'Date')) {
// 对Date对象做特殊处理
child = new Date(parent.getTime());
} else {
// 处理对象原型
proto = Object.getPrototypeOf(parent);
// 利用Object.create切断原型链
child = Object.create(proto);
}

// 处理循环引用
const index = parents.indexOf(parent);

if (index != -1) {
// 如果父数组存在本对象,说明之前已经被引用过,直接返回此对象
return children[index];
}
parents.push(parent);
children.push(child);

for (let i in parent) {
// 递归
child[i] = _clone(parent[i]);
}

return child;
};
return _clone(parent);
};

我们做一下测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function person(pname) {
this.name = pname;
}

const Messi = new person('Messi');

function say() {
console.log('hi');
}

const oldObj = {
a: say,
c: new RegExp('ab+c', 'i'),
d: Messi,
};

oldObj.b = oldObj;


const newObj = clone(oldObj);
console.log(newObj.a, oldObj.a); // [Function: say] [Function: say]
console.log(newObj.b, oldObj.b); // { a: [Function: say], c: /ab+c/i, d: person { name: 'Messi' }, b: [Circular] } { a: [Function: say], c: /ab+c/i, d: person { name: 'Messi' }, b: [Circular] }
console.log(newObj.c, oldObj.c); // /ab+c/i /ab+c/i
console.log(newObj.d.constructor, oldObj.d.constructor); // [Function: person] [Function: person]

当然,我们这个深克隆还不算完美,例如Buffer对象、Promise、Set、Map可能都需要我们做特殊处理,另外对于确保没有循环引用的对象,我们可以省去对循环引用的特殊处理,因为这很消耗时间,不过一个基本的深克隆函数我们已经实现了。

理解JavaScript--传递引用

发表于 2018-02-25 | 分类于 JavaScript | 阅读次数

七、传递引用


1.JavaScript中的基本类型传递

一个我们经常遇到的问题:“JS中的值是按值传递,还是按引用传递呢?”

由于js中存在复杂类型和基本类型,对于基本类型而言,是按值传递的.

1
2
3
4
5
6
7
var a = 1;
function test(x) {
x = 10;
console.log(x);
}
test(a); // 10
console.log(a); // 1

虽然在函数test中a被修改,并没有有影响到
外部a的值,基本类型是按值传递的.


2.复杂类型按引用传递?

我们将外部a作为一个对象传入test函数.

1
2
3
4
5
6
7
8
9
10
var a = {
a: 1,
b: 2
};
function test(x) {
x.a = 10;
console.log(x);
}
test(a); // { a: 10, b: 2 }
console.log(a); // { a: 10, b: 2 }

可以看到,在函数体内被修改的a对象也同时影响到了外部的a对象,可见复杂类型是按引用传递的.

可是如果再做一个实验:

1
2
3
4
5
6
7
8
9
10
var a = {
a: 1,
b: 2
};
function test(x) {
x = 10;
console.log(x);
}
test(a); // 10
console.log(a); // { a: 1, b: 2 }

外部的a并没有被修改,如果是按引用传递的话,由于共享同一个堆内存,a在外部也会表现为10才对.
此时的复杂类型同时表现出了按值传递和按引用传递的特性.


3.按共享传递

复杂类型之所以会产生这种特性,原因就是在传递过程中,对象a先产生了一个副本a,这个副本a并不是深克隆得到的副本a,副本a地址同样指向对象a指向的堆内存.

因此在函数体中修改x=10只是修改了副本a,a对象没有变化.
但是如果修改了x.a=10是修改了两者指向的同一堆内存,此时对象a也会受到影响.

有人讲这种特性叫做传递引用,也有一种说法叫做按共享传递.

理解JavaScript--this

发表于 2018-02-25 | 分类于 JavaScript | 阅读次数

五、this


1.什么决定了this的指向

  this一直是JavaScript中十分玄乎的存在,很多人为了避开这个琢磨不透的东西,选择了尽量少得运用this,但是不可否认的是,正是因为this的存在才使得JavaScript拥有了更加灵活的特性,因此,搞清楚this是每一个JavaScript学习者的必修课.

  this之所以让人又爱又恨,正是因为它的指向让人琢磨不透,在进行详细讲解之前,我们要搞清楚一个大前提,this的指向不是在编写时确定的,而是在执行时确定的.

1
2
3
4
5
6
7
8
9
10
11
12
obj = {
name: "Messi",
sayName: function () {
console.log(this.name);
}
};

obj.sayName(); //"Messi"

var f = obj.sayName;
f(); //undefind
console.log(f === obj.sayName); //true

  很明显,虽然f与obj.sayName是等价的,但是他们所产生的结果却截然不同,归根到底是因为它们调用位置的不同造成的.

  f的调用位置在全局作用域,因此this指向window对象,而window对象并不存在name因此会显示出undefind,而obj.sayName的this指向的是obj对象,因此会打印出"Messi".

  我们可以在以下代码中加入name = "Bale";来证明以上说法.

1
2
3
4
5
6
7
8
9
10
11
12
13
name = "Bale";
obj = {
name: "Messi",
sayName: function () {
console.log(this.name);
}
};

obj.sayName(); //"Messi"

var f = obj.sayName;
f(); //"Bale"
console.log(f === obj.sayName); //true

  大家一定会好奇,调用位置是如何决定obj.sayName的this指向obj对象,f却指向window对象呢,其中遵循什么规则吗?


2.默认绑定

  this一共存在4种绑定规则,默认绑定是其中最常见的,我们可以认为当其他三个绑定规则都没有体现时,就用的是默认的绑定规则.

1
2
3
4
5
6
7
name = "Bale";

function sayName () {
console.log(this.name);
};

sayName(); //"Bale"

  以上代码可以看成我们第一节例子中的f函数,它之所以指向window对象,就是运用了this默认绑定的规则,因为此实例代码中既没有运用apply  bind等显示绑定,也没有用new绑定,不适用于其他绑定规则,因此便是默认绑定,此时的this指向全局变量,即浏览器端的windowNode.js中的global.


3.隐式绑定

  当函数被调用的位置存在上下文对象,或者说被某个对象拥有或包含,这时候函数的f的this被隐式绑定到obj对象上.

1
2
3
4
5
6
7
8
function f() {
console.log( this.name );
}
var obj = {
name: "Messi",
f: f
};
obj.f(); // Messi


4.显式绑定

  除了极少数的宿主函数之外,所有的函数都拥有call apply方法,而这两个大家既熟悉又陌生的方法可以强制改变this的指向,从而实现显式绑定.

call apply可以产生对this相同的绑定效果,唯一的区别便是他们参数传入的方式不同.

call方法:
语法:call([thisObj[,arg1[, arg2[, [,.argN]]]]])
定义:调用一个对象的一个方法,以另一个对象替换当前对象。
说明:
  call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。
  如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。

apply方法:
语法:apply([thisObj[,argArray]])
定义:应用某一对象的一个方法,用另一个对象替换当前对象。
说明:
  如果 argArray 不是一个有效的数组或者不是 arguments 对象,那么将导致一个 TypeError。
如果没有提供 argArray 和 thisObj 任何一个参数,那么 Global 对象将被用作 thisObj, 并且无法被传递任何参数。

  第一个参数意义都一样。第二个参数:apply传入的是一个参数数组,也就是将多个参数组合成为一个数组传入,而call则作为call的参数传入(从第二个参数开始)。
  如 func.call(func1,var1,var2,var3) 对应的apply写法为:func.apply(func1,[var1,var2,var3]),同时使用apply的好处是可以直接将当前函数的arguments对象作为apply的第二个参数传入。  

1
2
3
4
5
6
7
8
9
function f() {
console.log( this.name );
}
var obj = {
name: "Messi",

};
f.call(obj); // Messi
f.apply(obj); //Messi

我们可以看到,效果是相同的,call apply的作用都是强制将f函数的this绑定到obj对象上.
在ES5中有一个与call apply效果类似的bind方法,同样可以达成这种效果,

Function.prototype.bind() 的作用是将当前函数与指定的对象绑定,并返回一个新函数,这个新函数无论以什么样的方式调用,其 this 始终指向绑定的对象。

1
2
3
4
5
6
7
8
9
10
11
12
function f() {
console.log( this.name );
}
var obj = {
name: "Messi",
};

var obj1 = {
name: "Bale"
};

f.bind(obj)(); //Messi ,由于bind将obj绑定到f函数上后返回一个新函数,因此需要再在后面加上括号进行执行,这是bind与apply和call的区别

5.new绑定

用 new 调用一个构造函数,会创建一个新对象, 在创造这个新对象的过程中,新对象会自动绑定到Person对象的this上,那么 this 自然就指向这个新对象。
这没有什么悬念,因为 new 本身就是设计来创建新对象的。

1
2
3
4
5
6
function Person(name) {
this.name = name;
console.log(name);
}

var person1 = new Person('Messi'); //Messi


6.绑定优先级

通过以上的介绍,我们知道了四种绑定的规则,但是当这些规则同时出现,那么谁的优先级更高呢,这才有助于我们判断this的指向.
通常情况下,按照优先级排序是:
new绑定 > 显式绑定 >隐式绑定 >默认绑定

我们完全可以通过这个优先级顺序判断this的指向问题.


7.ES6箭头函数中的this

箭头函数不同于传统JavaScript中的函数,箭头函数并没有属于自己的this,它的this是捕获其所在上下文的 this 值,作为自己的 this 值,并且由于没有属于自己的this,箭头函数是不会被new调用的.

MDN文档中关于箭头函数的实例很清楚的说明了这一点.

在 ECMAScript 3/5 中,这个问题可以通过新增一个变量来指向期望的 this 对象,然后将该变量放到闭包中来解决。

1
2
3
4
5
6
7
8
9
10
function Person() {
var self = this; // 也有人选择使用 `that` 而非 `self`.
// 只要保证一致就好.
self.age = 0;

setInterval(function growUp() {
// 回调里面的 `self` 变量就指向了期望的那个对象了
self.age++;
}, 1000);
}

除此之外,还可以使用 bind 函数,把期望的 this 值传递给 growUp() 函数。

箭头函数则会捕获其所在上下文的 this 值,作为自己的 this 值,因此下面的代码将如期运行。

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

setInterval(() => {
this.age++; // |this| 正确地指向了 person 对象
}, 1000);
}

var p = new Person();

当然,我们用babel转码器,也可以让我们更清楚理解箭头函数的this

1
2
3
4
5
6
7
8
// ES6
const obj = {
getArrow() {
return () => {
console.log(this === obj);
};
}
}
1
2
3
4
5
6
7
8
9
// ES5,由 Babel 转译
var obj = {
getArrow: function getArrow() {
var _this = this;
return function () {
console.log(_this === obj);
};
}
};

理解JavaScript--属性

发表于 2018-02-25 | 分类于 JavaScript | 阅读次数

六、属性


1.JavaScript中的对象

JavaScript中的对象一般分为三类:内置对象(Array, Error, Date等), 宿主对象(对于前端来说指的是浏览器对象,例如window), 自定义对象(指我们自己创建的对象).

因此,我们主要讨论的内容是围绕自定义对象展开的,今天我们就对象的属性进行深入地探究.


2.属性的创建

我们先定义一个对象,然后对其赋值:

1
2
var person = {};
person.name = "Messi";

以上操作相当于给person对象建立了一个name属性,且值为'Messi'.

那么这个赋值的过程具体的原理是什么呢?

首先,我们创建了一个’空’对象,之所以我们打上引号,是因为这并不是一个严格意义上的空对象,因为在建立这个对象的过程中,JavaScript已经为这个对象内置了方法和属性,当然是不可见的,在属性的建立过程中就调用了一个隐式的方法[[put]].

大概的创建过程是,当属性第一次被创建时,对象调用内部方法[[put]]为对象创建一个节点保存属性.

1
2
3
4
5
st=>start: person
e=>end: persen.name = "Messi"
io1=>inputoutput: [[put]]

st->io1->e


3.属性的修改

我们对上例中的代码做一下修改:

1
2
3
var person = {};
person.name = "Messi";
person.name = "Bale";

很显然,name被创建后,该属就被进行了修改,原属性值Messi被修改为Bale,那么这个过程又是如何发生的呢?

其实对象内部除了隐式的[[put]]方法,还有一个[[set]]方法,这个方法不同于[[put]]在创建属性时调用,而是在同一个属性被再次赋值的时候用于更新属性进行的调用.

1
2
3
4
5
st=>start: person.name = "Messi"
e=>end: persen.name = "Bale"
io1=>inputoutput: [[put]]

st->io1->e

4.属性的查询

判断一个属性或者方法是否在一个对象中,通常有两种方式.
in操作符方式:

1
2
3
4
var person = {
name: "Messi"
};
console.log("name" in person); //true

hasOwnProperty方法:

1
2
3
4
var person = {
name: "Messi"
};
console.log(person.hasOwnProperty("name")); //true

5.属性的删除

删除一个属性,最正确的方式是用delete方法,一个错误的方式是将该属性赋值为null,该方式的错误之处在于赋值null相当于调用了[[set]]方法把原属性值更改为了null,这个保存属性的节点依然存在,而用delete方法便能彻底删除这个节点.

1
2
3
4
5
var person = {
name: "Messi"
};
delete person.name;
console.log("name" in person); //false

6.属性的枚举

我们通常用for...in枚举对象中的属性,它会将属性一一返回.
在ES5中引入了一个新的方法Object.key(),不同之处在于,它可以将结果以数组的形式返回

1
2
3
4
5
6
7
8
9
10
11
var person = {
name: "Messi",
age: 29
};

for(var pros in person ) {
console.log(pros); // name age
}

var pros = Object.keys(person);
console.log(pros); //[ 'name', 'age' ]

值得注意的是,并非所有的属性都是可枚举的,例如对象自带的属性length等等,因此我们可以用propertyIsEnumerable()方法来判断一个属性是否可枚举.

1…3456
pinocchio

pinocchio

喜欢探索,热爱编程

59 日志
18 分类
42 标签
RSS
Github E-mail
© 2017 - 2020 pinocchio
本站访客数:
博客全站共38.9k字