async_hooks

async_hooks是nodejs在8.2.1后引入的特性,目前仍然是Experimental状态。它被用来追踪NodeJS中异步资源的生命周期。

在async_hooks特性加入之前,想要了解异步调用上下文或追踪异步调用逻辑是件比较困难的事情:

  • 最早在v0.11中有实现AsyncListener,但在v0.12时被移除
  • 在Node6和7时,有非官方的AsyncWrap实现,指定回调函数监听异步资源的创建、调用前、调用后时机

async_hooks友好地解决了异步资源创建、调用的追踪问题:

  • 异步资源代表一个关联了回调的对象,回调可能被调用1次或多次,比如net.createServer()里的connect事件或fs.open()AsyncHook不区分这些场景,统一视作异步资源
  • 每一个异步上下文都有一个关联的id,即asyncId。asyncId是从1开始递增的,同一个async上下文中的id相同(在未enable async hook时,promise执行不会被分配asyncId)。executionAsyncId()可以获取当前异步上下文的asyncId,triggerAsyncId()获取触发当前异步上下文的异步上下文。借助asynId和triggerAsyncId可以追踪异步的调用关系和链路。
  • async_hooks.createHook()函数可以注册异步资源生命周期中init/before/after/destroy/promiseResolve事件的监听函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const async_hooks = require('async_hooks')
// ID of the current execution context
const eid = async_hooks.executionAsyncId()
// ID of the handle responsible for triggering the callback of the
// current execution scope to call
const tid = async_hooks.triggerAsyncId()
const asyncHook = async_hooks.createHook({
// called during object construction
init: function (asyncId, type, triggerAsyncId, resource) { },
// called just before the resource's callback is called
before: function (asyncId) { },
// called just after the resource's callback has finished
after: function (asyncId) { },
// called when an AsyncWrap instance is destroyed
destroy: function (asyncId) { },
// called only for promise resources, when the `resolve`
// function passed to the `Promise` constructor is invoked
promiseResolve: function (asyncId) { }
})
// starts listening for async events
asyncHook.enable()
// stops listening for new async events
asyncHook.disable()

executionAsyncId和triggerAsyncId

调用executionAsyncIdtriggerAsyncId函数获取当前异步上下文的asyncId和triggerAsyncId。

executionAsyncId的返回值由运行时决定,triggerAsyncId可以返回当前上下文的触发原因上下文id。见下面的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const server = net.createServer((conn) => {
// Returns the ID of the server, not of the new connection, because the
// callback runs in the execution scope of the server's MakeCallback().
async_hooks.executionAsyncId();
// The resource that caused (or triggered) this callback to be called
// was that of the new connection. Thus the return value of triggerAsyncId()
// is the asyncId of "conn".
async_hooks.triggerAsyncId();
}).listen(port, () => {
// Returns the ID of a TickObject (i.e. process.nextTick()) because all
// callbacks passed to .listen() are wrapped in a nextTick().
async_hooks.executionAsyncId();
// Even though all callbacks passed to .listen() are wrapped in a nextTick()
// the callback itself exists because the call to the server's .listen()
// was made. So the return value would be the ID of the server.
async_hooks.triggerAsyncId();
});

createHook

更常用地,我们使用async_hooks.createHook创建异步资源的钩子,注册异步资源生命周期各阶段的回调函数,目前支持init/before/after/destroy/promiseResolve这几种。

注意:打印信息到控制台也是一个异步操作,console.log()会触发AsyncHooks的各个回调。因此AsyncHook回调内使用console.log()或类似异步日志打印,会造成无限递归。一种解决办法是使用fs.writeFileSyncprocess._rawDebug这种同步日志操作。

1
2
3
4
5
6
7
8
9
const fs = require('fs');
const util = require('util');

function debug(...args) {
// Use a function like this one when debugging inside an AsyncHooks callback
fs.writeFileSync('log.out', `${util.format(...args)}\n`, { flag: 'a' });
// OR
process._rawDebug(`${util.format(...args)}\n`);
}

init(asyncId, type, triggerAsyncId, resource)

可能会触发异步事件的资源构造时调用。这不代表后面的before/after事件回调会在destroy回调触发,只是说有这个可能。举个例子:

1
2
3
require('net').createServer().listen(function() { this.close(); });
// OR
clearTimeout(setTimeout(() => {}, 10));

参数解释如下:

  • asyncId 异步资源id
  • type 异步资源类型,字符串枚举值,具体参见官方文档
  • triggerAsyncId 触发当前异步资源创建的异步上下文的asyncId
  • resource 被初始化的异步资源对象

triggerAsyncId表示的是资源创建的原因,async_hooks.executionAsyncId()表示的是资源创建的时机。如下面例子里体现的一样:

1
2
3
4
5
6
7
8
9
async_hooks.createHook({
init(asyncId, type, triggerAsyncId) {
const eid = async_hooks.executionAsyncId();
fs.writeSync(
1, `${type}(${asyncId}): trigger: ${triggerAsyncId} execution: ${eid}\n`);
}
}).enable();

require('net').createServer((conn) => {}).listen(8080);

nc localhost 8080后,打印信息如下:

1
2
TCPSERVERWRAP(5): trigger: 1 execution: 1
TCPWRAP(7): trigger: 5 execution: 0

before(asyncId)

在异步操作初始化完成(如TCP服务器接收新连接)或资源准备完成(写数据到磁盘),准备执行回调时触发。入参asyncId即这个异步资源的ID。before事件可能会触发0~N次。

  • 0次,异步操作被撤销
  • > 1次,持久化的异步资源,如TCP服务器

after(asyncId)

回调执行完成后立即触发。当执行回调过程中有未捕获异常,会在触发“uncaughtException”事件后触发。

destroy(asyncId)

当asyncId对应的异步资源被销毁时调用。有些异步资源的销毁要依赖垃圾回收机制,所以当引用了传递到init函数的resource时,destory事件可能永远不会被触发,从而造成内存泄漏。

promiseResolve(asyncId)

当Promise构造器中的resolve函数被执行时,promiseResolve事件被触发。有些情况下,有些resolve函数是被隐式执行的,比如.then函数会返回一个新的Promise,这个时候也会被调用。

new Promise((resolve) => resolve(true)).then((a) => {});语句执行时,会顺序触发下列函数:

1
2
3
4
5
6
init for PROMISE with id 5, trigger id: 1
promise resolve 5 # corresponds to resolve(true)
init for PROMISE with id 6, trigger id: 5 # the Promise returned by then()
before 6 # the then() callback is entered
promise resolve 6 # the then() callback resolves the promise by returning
after 6

AsyncHook实例定义好后,需要通过enable开启。可以使用disable关闭AsyncHook的回调执行。

下面是一个AsyncHook的实例:

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
let indent = 0;
async_hooks.createHook({
init(asyncId, type, triggerAsyncId) {
const eid = async_hooks.executionAsyncId();
const indentStr = ' '.repeat(indent);
fs.writeSync(
1,
`${indentStr}${type}(${asyncId}):` +
` trigger: ${triggerAsyncId} execution: ${eid}\n`);
},
before(asyncId) {
const indentStr = ' '.repeat(indent);
fs.writeFileSync('log.out', `${indentStr}before: ${asyncId}\n`, { flag: 'a' });
indent += 2;
},
after(asyncId) {
indent -= 2;
const indentStr = ' '.repeat(indent);
fs.writeFileSync('log.out', `${indentStr}after: ${asyncId}\n`, { flag: 'a' });
},
destroy(asyncId) {
const indentStr = ' '.repeat(indent);
fs.writeFileSync('log.out', `${indentStr}destroy: ${asyncId}\n`, { flag: 'a' });
}
}).enable();

require('net').createServer(() => {}).listen(8080, () => {
// Let's wait 10ms before logging the server started.
setTimeout(() => {
console.log('>>>', async_hooks.executionAsyncId());
}, 10);
});

在启动服务器后,打印信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
TCPSERVERWRAP(5): trigger: 1 execution: 1
TickObject(6): trigger: 5 execution: 1
before: 6
Timeout(7): trigger: 6 execution: 6
after: 6
destroy: 6
before: 7
>>> 7
TickObject(8): trigger: 7 execution: 7
after: 7
before: 8
after: 8

异常处理

可以直接参考官方文档描述

可以用来干嘛

一个最为人知的使用场景是我们下面会提到的CLS(Continuation-local-storage)。cls-hooked库通过async_hooks建立了context对象和当前async执行上下文的关系,从而在整个执行链(execution chain)上维护一个统一的数据存储。

还有一个是结合Performance Timing API这样的性能监测工具诊断整个异步操作流程的性能。比如这篇文章所介绍的。

参考

CLS

Continuation-local storage(CLS)类似线程编程里的线程存储,不过基于nodeJS风格的链式回调函数调用。它得名于函数式编程中的Continuation-passing style,旨在链式函数调用过程中维护一个持久的数据。

在node V8之前,分别基于AsyncListener和AsyncWrap实现。在V8后,基于async_hook实现的库名为cls-hooked。但使用方法一致。

这里借用cls README里的一个例子。假设你写了一个获取用户信息的模块,将获取到的用户信息放在session中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// setup.js

var createNamespace = require('cls-hooked').createNamespace;
var session = createNamespace('my session');

var db = require('./lib/db.js');

function start(options, next) {
db.fetchUserById(options.id, function (error, user) {
if (error) return next(error);

session.set('user', user);

next();
});
}

之后,需要将用户信息转化为一个HTML文档,你在另外一个文件中定义了转换函数,并从session中取出你想要的用户信息。

1
2
3
4
5
6
7
8
9
10
11
// send_response.js

var getNamespace = require('cls-hooked').getNamespace;
var session = getNamespace('my session');

var render = require('./lib/render.js')

function finish(response) {
var user = session.get('user');
render({user: user}).pipe(response);
}

使用

cls的使用围绕namespace展开,你可以根据需要自由组织namespace,需要持久化的信息读写在namespace的context上进行。

  • cls.createNamespacecls.getNamespace 创建和获取一个namespace
  • cls.destroyNamespacecls.reset 删除一个namespace和重置所有namespace
  • ns.getns.set 在namespace的context上读取和设置持久化数据
  • ns.runns.runAndReturnns.runPromise 在给定context下执行函数
  • ns.bindns.bindEmitter 绑定context到给定函数或eventEmitter
  • context 维护持久化数据的plain object

更多API参考文档

实现原理

正如上面所说,“cls-hooked库通过async_hooks建立了context对象和当前async执行上下文的关系”。下面有张图通过例子描述了cls的工作过程:

CLS workflow

简单拆解一下:

  • 首先,我们有一个典型的web server和应用上的中间件,我们在整个应用的生命周期里创建一个cls的namespace。
  • 新请求到达中间件时,cls通过ns.run(别的方式也行)创建一个空的cls context,并入栈该context,设置为active context。
  • 由于cls内部注册了AsyncHook,在init阶段,在Map中关联对应active context到当前asyncId。从而有异步操作(如查数据库)时,此前入栈的context就和操作的asyncId对应上。此后get
    、set操作都会针对同一active context进行。
  • 异步操作完成后,after回调触发,active context变成undefined,同时出栈当前context。当destroy回调触发时,会将关联到asyncId的context从Map中移除。

在cls-hooked实现中,

  • ns.getns.setns.active相关联
  • ns.active通过ns.enterns.exit变更或者在init回调中从contextMap中改变。
  • ns.enterns.exitinit回调最终都经由ns.runxxxns.bindxxx得到初始的context
  • cls-hooked借助async_hook和ns.enterns.exit保证异步流程中context和异步上下文的正确对应关系

考虑到cls-hooked的js代码可读性,可维护性和工程角度上还有改善空间,基于上面的原理,做了ts的重构,源码见这里(待补充),供大家参考和学习cls-hooked。

追踪logId

醉翁之意不在酒

有了cls的帮助,我们就可以利用它帮我们持久化logId,避免“continuation-passing-context”。可以写一个中间件,为req、res包装context,同时为每次请求持久化logId。在后面的controller、services这些位置就可以拿到之前持久化的logId。

一个express风格的中间件类似下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const cls = require('cls-hooked')
const uuidv4 = require('uuid/v4')

const clsNamespace = cls.createNamespace('app')

const clsMiddleware = (req, res, next) => {
// req and res are event emitters. We want to access CLS context inside of their event callbacks
clsNamespace.bind(req);
clsNamespace.bind(res);

const logId = uuidv4();

clsNamespace.run(() => {
clsNamespace.set('logId', logId);

next();
})
}

// controller.js
const controller = (req, res, next) => {
const traceID = clsNamespace.get('logId');
}

在这个思路的基础上,有类似cls-rtracercls-proxify这样的库,提供针对express、koa、fastify等常见后端框架的中间件,只需简单指定配置,便可以在请求的生命周期里透传logId,免去“continuation-passing-context”的尴尬,对已有代码侵入性也很小。有需要透传logId,但并不想(或暂时不能)使用后端框架的场景下可以考虑使用这种方案。

参考

参考《社会心理学》 David G. Myers 第8版

社会心理学 Part 1 - 导论 & 社会思维 社会心理学 Part 3 - 社会关系 社会心理学 Part 4 - 应用

这部分主要讨论社会给个人的影响。

基因、文化和性别

基因和文化的影响

  • 进化观点:强调人类的联系、相似性
  • 文化观点:强调人类的多样性

自然选择过程决定了我们的许多共性特征。进化心理学即基于此研究自然如何影响那些适应特定环境的生理特征,以及有利于基因存活和延续的心里特征和社会行为。

在共有生理基础上,文化赋予人类以多样性。文化更强调人类的适应性。我们的大多数行为都是受社会影响的。当与来自不同文化背景的人一起工作、生活时,会更容易理解文化如何影响我们和文化间的差异性。文化中会自然形成强迫人们遵从传统的社会行为的期待,即社会规范私人空间是一种我们自己与他人间维持润滑和缓冲的空间,不同个体间需求的私人空间也不同。

文化除了差异性,还具有内在的统一性,比如对乱伦的限制,对战犯的处理上。而且罗杰·布朗提出,人和人之间不仅会形成某种等级地位,在和地位较高的人说话时,语气往往更尊敬(就像和陌生人说话一样),而和地位较低的人则像和熟人聊天,进而可以推出:

  1. 强调沟通的形式不仅反映社会距离也反映社会地位
  2. 亲密关系往往由较高地位的人控制。地位更高的人是亲密关系发展的制定者。比如开会发言、约会吃饭。

社会角色

  • 社会角色比角色扮演者本身有更长久的生命力。
  • 一种社会角色伴随的是一系列的社会规范
  • 社会角色具有强大的影响力,我们倾向于接纳自己的角色

在实验中,地位更高的人认为自己更应该受到优待,且具有更高的能力,尽管地位是实验分配的。相反,较低下的社会角色会主动削弱自己的自我效能感,自我赋能可以打破这种困境。

角色互换可以帮助人们更好地理解对方,毕竟,人类大部分的冲突和争论都源自人们过于关注自己的意见而非寻找问题的正确答案

性别相似性和差异性

对于个体意识以及社会关系而言,种族和性别是最重要的两个维度。对性别差异上的研究不能被用来验证偏见和加深刻板印象。男性和女性有很多性别上的不同:

  • 男性有更多神经元,女性有更多神经连接
  • 男性更有独立性,女性更具联系性。女性比男性更注重亲密关系。女性更易用关系性的词汇描述自己,乐于接受他人帮助,体验更多和关系相关的情感,努力使关系更协调;男性在谈话时常常关注任务和大群体的关系。
    • 工作上,男性更倾向于看重报酬、提升机会、挑战和权力,而女性更看重合理时间安排、私人关系、帮助他人的机会,如护士、教师
    • 女性在家庭上会花更多时间照顾孩子和老人,相互支持感对女性的婚姻满意度是极为关键的
    • 女性比男性更可能为他人的经历产生、表现出共情的反应。有一种解释是,女性具有更强的理解他人情绪的能力。
    • 女性在记忆面部特征和其他外部特征上更为出色,非言语表达情绪的能力也很出色。男人在传达愤怒上更明显。
  • 几乎所有社会,都是男性处于统治地位。
    • 男性比女性更关心社会统治问题
    • 男性交流的方式可以加强他们的社会权力,在没有界定领导角色的场景下,男性倾向于指示性的领导方式,女性倾向于民主化的方式
    • 男性比女性更强调胜利、超期和控制他人,同样也更爱冒险。
    • 男性的谈话方式可以反映他们对独立的关注,而女性更注重关系
  • 男性承认自己比女性有更多攻击行为。有研究显示,女性苏沪会发起更多攻击行为,但是男性更容易造成伤害
  • 性特征
    • 男性比女性更可能发生性活动
    • 男性进行更多性幻想,态度也更开放,而且试图寻找更多性伴侣,更容易引发性唤醒,更多要求性爱,很少拒绝性爱,且更偏爱形式各异的性爱
    • 人类学家唐纳德·西蒙指出,“在世界各地,性都被理解成为女性所拥有的,男性想得到的东西”
    • 在185个国家的调查中发现,男人越稀有,女性怀孕的比率就越高。而当女人稀少时,女性性行为的市场价值就会提升,她们会要求更高的价格
    • 性幻想上,女性更注重感情

进化和文化交织地影响着性别行为。

进化对性别行为的影响

两性在后天可养成的行为上,如果面临相同的适应挑战,会趋向相同。而在约会、婚配、繁殖行为等天生行为上会存在差异。由于自然会选择那些有助于基因遗传的特性,所以我们天生就追求那些增加基因遗传性的生活方式。从而:

  • 女性会更加小心考察男性的身体健康和资源状况,谨慎处理自己的繁殖机会。男性则需要和他人竞争,以便把自己的基因遗传下去。简言之,男性寻求更广泛的繁殖、女性寻求更明智的繁殖
  • 男性偏爱有生殖力旺盛外表特征的女性,而女性偏爱有财产和地位这些能为后代提供足够保护和抚养的男性

在人进入中年和老年时,男女性会渐渐趋同,女性变得独断自信,而男性可能更好共情更少支配他人。一方面是激素的影响减弱,另一方面是社会期待对个体的限制减弱。

文化对性别行为的影响

文化是大群体共享的代代相传的“共同假设”。文化对不同性别的期待界定了性别角色。在实验中,男女性都趋向于让自己的表现和对方的性别角色期望相符合。由于文化的地域性和年代性,不同地区和年代下的性别角色也是在变化的。总体上讲,男女的性别角色已经没有以前那么大了。

在文化的代代相传上,教养论认为,父母抚养孩子的方式决定孩子成为什么样的人。因为孩子会接受父母的很多价值观。而后在一些调查研究中发现,孩子的人格差异受同伴的影响更大。孩子通常从更大的孩子身上学习,直到那些和父母同一代的年轻人。总结来说,

  • 父母一代对孩子一代的联系是松散的,文化的传承是间接的,孩子受同伴影响更多
  • 不过父母可以决定孩子身边的同伴,如学校、社区

整体来看,生物因素和文化因素交互影响我们的态度和行为。与此同时,人是具有主观能动性的,人本身具有改变自己态度和行为的能力。

  • 同一种社会情境付不同人有不同的影响
  • 人们可以选择自己所处的情境
  • 人们能创造自己的环境

从众

什么是从众

从众是指根据他人而做出的行为或信念的改变,有三种形态:

  • 顺从,为了得到奖励或避免惩罚
  • 服从,通过明确命令引起的顺从行为
  • 接纳,真诚的内在的从众行为

从众的经典研究

这里介绍三例影响较大的经典研究:

  • 谢里夫的规范形成研究(社会传染)
  • 阿施的群体压力研究(社会压力)
  • 米尔格拉姆的服从实验

它们分别从三个不同角度研究了在外界压力逐步增大的情境下,我们的从众行为是怎样形成和表现的。

谢里夫的规范形成研究

Muzafer Sheriff研究了在暗示下,个体是怎样形成群体规范的。通过一个巧妙的关于光点似动现象的实验,谢里夫发现了个体的易受暗示性。我们对现实的看法未必来自我们自己的观点。一个人的咳嗽、笑或打呵欠会引起周围人的效仿,即社会传染效应。

这种暗示还会在更大范围内传递。如自杀事件的报道会引起自杀率的轻微上升,报道得越厉害,增加得就越多。一方面因为青少年是易感人群,容易模仿;另一方面是在暗示的作用下,我们不能透过现象寻找本质原因。“群体癔症”也是因为社会传染而引起的集体妄想表现。

阿施的群体压力研究

谢里夫的实验针对的是模糊的现实场景,而阿施则通过同谋实验者为被试制造出“刻意”的社会压力,研究此时的从众行为。具体表现为让被试一个一个回答简单问题,在前面的同谋者都赞同明显错误答案时,研究发现有一小部分人在不安和内心冲突后赞同了明显错误的答案。

米尔格拉姆的服从实验

阿施的群体压力实验距离现实较远,社会压力并没有强迫个体服从。米尔格拉姆通过实验研究在社会压力直接强迫个体时,从众行为是如何表现的。具体表现为,邀请同谋扮演学习者和被试扮演教师,隔离开学子和和教师,让教师考察学习者,并在学习者给出错误答案时,在研究者的要求下提升点击学习者的电压。电压从15伏到450伏(当然电击是假的,学习者的痛苦反应也只是录音)。

被试的所有人都认为在135伏左右会不服从研究者的命令。实际上,有65%的被试进行实验一直到450伏,而不顾学习者的“哭喊”和“抗议”。实验结论在发表后,被质疑是否具有伦理问题,让被试在实验后质疑自我。对此米尔格拉姆解释实验结束后,会告诉被试真相,在事后回访时也只有1%的被试表示很遗憾。

服从行为的影响因素

受害者的情感距离

米尔格拉姆的被试在无法看到和听到学习者(同样学习者也看不到被试)时,其行动表现出的同情最少,相反在实验中可以听到抗议,甚至可以看到被试时,完全服从的比例有所下降。

我们很容易贬低远离自己或失去个性的人。甚至对于巨大大灾难,人们也会无动于衷。

  • 火车轨道困境中,人们对于绑在火车道上的单个人的同情要甚于火车道上的一群人
  • 人们对于个性化的人是最富有同情心的,这就是为什么人们通常用令人感动的照片赋予饥饿人群、动物权利、新生儿以个性化
  • 同样,如果对受害者个性化,无辜的受害者会博得更多同情。一个误落矿井的男孩是一条鲜活的生命,而一场核战争的牺牲者只是一个数字

权威的接近和合法性

米尔格拉姆的实验中发现,要求命令的发出者在空间距离的接近性会增加服从度。当命令通过电话发出时,服从度有较大下降。同时权威必须要具有合法性,当研究者告知仪器可以自动进行实验,换作另外一个人代替他发出命令时,被试中出现了反叛的现象。

权威机构和释放效应

  • 机构声望能增加命令的合法性。实验在耶鲁大学进行和在普通城市的普通办公楼进行时,完全服从的比例有所下降。
  • 尽管群体在被直接强迫时会有从众表现,但当有参与者接二连三地表示反对和不满时,释放效应会引起对命令的强烈反抗

从众研究的反思

  • 当外界影响超过内在信仰,个人知觉被淹没时,态度便无法决定行为。强有力的社会压力(命令)会超越力量较弱的因素(受害者的抗争)。可怕的是,结合之前提到的登门槛现象,当电压一点点增大时,被试早已降低了态度和行为的不协调感,内化了行为的合法性,很容易接受之前被强迫的命令。即顺从滋生接纳。施暴者会在暴力一点点升级时,贬低被害者以减轻负罪感,而慢慢从命令的执行者变成冷漠的杀人机器。
  • 社会情境具有极大的影响力,它虽然没有明文规定,但指导着公众行为。比如,米尔格拉姆在要求学生主动要求乘坐地铁的乘客让座时,有过半人都让了座,而同时学生也发现要这样做真得很难。情境有时会诱使普通人赞同谬误或向邪恶屈服。当人们分散分工时,邪恶似乎更容易进行。
  • 人们对罪恶的执行者存在基本归因错误,误认为他们本来就是内心邪恶的人。从而忽视了邪恶行为的产生原因主要来自外在环境,我们离他们其实并不远。普通人,可以心中并没有任何仇恨,只是做本职工作,也可以成为可怕破坏性行动的执行者。善良的人们有时也会堕落,而且他们会对自己不道德的行为进行合理化归因。

从众行为的影响因素

  • 任务难度:在阿施的社会压力实验中,任务判断非常困难或让被试感觉无法胜任时,从中比率会升高。
  • 群体规模:在实验室实验中,群体规模增加到5个人的过程中,从众行为会极大提升,但大于5个人时,从众程度增加便不明显,甚至有所下降。多个小群体的一致意见会使观点更可信
  • 群体一致性:群体一致性被破坏时,群体的社会影响力会下降。这也是为什么当你能找到和你立场一致的人时,你为某件事站出来就容易得多。同时,观察到其他人的异议时,即使这种异议时错误的、和自己矛盾的,也会增强我们自己独立性
  • 群体凝聚力:群体外的人提出的“少数派观点”对我们的影响小于我们群体内“少数派观点”对我们的影响。一个群体的凝聚力越强,对成员的影响力就越大。
  • 地位:我们会避免与地位低的或被自己嘲笑的人意见一致,而更向地位高的人靠拢。这里的地位高低完全由我们自己的观察决定。
  • 公开反映:与面对群体比,在私人空间里,我们更容易坚持自己的信仰
  • 事前承诺:个体一旦对自己的立场作出了公开承诺,就很少屈服于社会压力。公开的承诺会让人无法后退

从众原因和差异性

主要来自两种影响:

  • 规范影响,避免被群体拒绝,避免受到不合规范的惩罚。有时,高昂的代价会迫使人们支持自己不相信的东西,或至少压制自己的反对意见。即我们想得到他人的喜爱和赞赏
  • 信息影响,他人的反应会影响我们对模糊情境的解释,和群体保持一致会使人们更容易证实自己的决策树正确的。即我们想要做出正确的行为

个性和文化能判断从众行为对个体的影响程度。就个性来讲,几乎是和环境一起影响着人们的从众行为。而文化中,在不同时不同地对人们从众行为的指示也是不一样的。

从众行为的抵制

逆反

个体非常看重自己的自由感和自我效能感时,如果社会压力很明显,以至于威胁到个体自由感时,会引起个体反抗。逆反,即人们采取行动保护自己的自由感。努力限制自由会引起“反从众”,比如可以用来解释性胁迫。

坚持独特性

个体认为自己是独特的,且这种独特感是中等程度时,会产生良好的自我感觉。因此,我们都在努力保持一定程度的独特性,这也是潮流多变的原因。在实验中,那些听到其他人发表观点和自己相同的人,反而会改变自己的观点来维护自己的独特性。

把自己看成独特的个体也会出现在“自发性自我概念中”,即上一篇提到的“虚假独特性”。自我介绍时,我们会倾向于描述自己较独特的一面。有种解释是,只有这样,个体与众不同时,个体才会意识到自我存在。更进一步,我们不仅追求与众不同,还追求正确方向上的独特性,即好于众人

后记

极致的个体主义里,不把任何事情归功于任何人,也不期望从任何人那里得到什么,孤立地看待自己的习惯,好像整个命运掌握在自己手里。相反,集体主义中,学生通常穿着校服避免个性,显示团结一致,抑制个体的冲突以求融洽相处。

可能求同存异是一种折中的更好的融合,平衡自己的独立性需要和社会关系需要,私密性和社会性,个体性和同一性。

说服

说服也是社会对我们的一种常见影响,在生活中,说服几乎无处不在。对个人而言,说服有利有弊,主要由信息背后的目的和包含的内容决定,我们称好的说服为“教育”,不好的说服为“洗脑”。有人认为文化塑造是从上而下的,有一部分中坚分子控制着信息观念的传播。

说服,即个体从知晓信息到做出行动的过程。在对人的作用上,主要分为两种途径:

  • 中心途径,当人们能全面系统地对某个问题进行思考时,关注论据合理性时,更多使用中心途径。即知晓->理解->相信->记住->行动。比如电脑等数码产品的广告
  • 外周途径,当人们忙于其他事情或不关注论据本身时,更多使用外周途径。这时,人们不会关心论据是否让人信服,而会“跟着感觉走”。比如饮料、烟草广告。

相比之下:

  • 中心途径需要人详细分析论据,需要时间,往往能形成更持久的行为改变
  • 外周途径通常基于启发性规则(如相信专家),形成较快,用于形成初步看法,行为改变并不持久

说服的要素

说服主要由4个部分组成:

  1. 传达者
  2. 信息内容
  3. 传递途径
  4. 听众

影响说服的因素实在很多,需要针对上述组成部分分开讨论和研究。

传达者

传达者可以影响说服的效果主要体现在下面一些方面。

可信度

信源的可信度对说服效果有积极影响。然而存在睡眠者效应,即人们容易遗忘信源或信源和信息联系之后的延滞性说服。体现在,信源可信度对说服起到的帮助会随着时间流逝而消退,相反那些可信度低的人影响力则会随着时间流逝而增加。

  • 可知觉的专家性,可以通过传达公众赞同的观点、被公认的学识渊博者引介以及自信的表达方式达到
  • 可知觉的信赖性,包括直视质疑者、听起来不像在说服自己、站在自身利益对立面(如一个自私的人突然间为社会奉献)等方式。同时,较快的语速也可以增加发言人的可信度。这是因为除了给人自信的暗示外,较快的语速通常也无法留给听众思考和留给质疑者发问。

吸引力和偏好

传达者的个人魅力也很重要。个人偏好可以让我们乐于接受传达者的观点,或在事后引发积极的联想。比如当论点来自一个漂亮的人时,往往具有更大的影响力。

但同时,就像我们选择牙膏、桌游游戏一样,我们也倾向于相信那些和我们相似的人。Goethals和Nelson发现,当主题偏重主观偏好时,相似性影响力更大;主题偏重客观偏好时,可信度影响力更大。所以,在预测明天的天气上,我们并不会相信我们的朋友而是天气预报员。

信息内容

信息内容同样有许多维度可以衡量。

理性or感性

听众教育程度较高或善于思辨或积极参与时,更容易接受理性信息说服;反之更容易接受感性信息说服。如果个体的初始态度来自情感,那么就更易受感性论点说服;反之,理性的论点就更有效。同时,新的态度形成需要比上次更多的信息。

  • 信息和好消息联系在一起时,说服力更强。它会让个体做决定时更冲动,更多依赖外周线索。比如,很多消费品广告都会尽量营造出快乐的氛围
  • 唤起恐怖效应的形象化消息,同样会起到充分的反馈。恐惧程度越高,回应越多。负面信息会使人们知觉相关积极行为的规范性。比如要求在烟草广告上明确展示让人不适的图片。同时,唤起恐惧心理后,需要同时告诉一个解决的方法,这样才会更有效。否则,人们可能会否定这个信息。

差异程度

传达让人不舒服的信息会引起质疑。而论点在人们可接受范围内时,他们更具有开放性。差异大小对可信度影响来自两个方面:

  • 传达者可信度,可信度很高时,信息接收者会接受一个较大差异的论点。比如专家对文献的解释
  • 信息接受者的参与程度,积极参与者能够接收的观点范围较小。

单方面or双方面

即仅从论点角度还是相反两个角度。这取决于听众:

  • 单方面论证对已持赞同态度的人更有说服力;双方面论证对持反对态度的人更有效
  • 对相反观点有所了解的人,会更易接收双方面论证。那些聪明的人听到单方面论证时,会认为信息传递者存在偏见。所以许多政客面向对政治有所了解的团体演讲时,会对对立观点加以回应。

首因or近因

首因效应很普遍,即最先出现的信息更具说服力,类似沉锚效应、皮格马列翁效应。近因是指,对发生较近的记忆更信服。

  • 信息接连出现、且听众听过后有反应时间时,首因效应更明显
  • 信息出现时间是分离的,且要求听众听完第二个意见后立即判断时,近因效应更明显

沟通渠道

我们做出行动时,会将行为背后隐藏的观念放大。相对应的,以经验为基础的态度更自信、稳定。

主动or被动接受

通常来说,被动接受消息对于说服几乎是无效的。但是不总是如此,比如在总统大大选时,花钱更多的候选人赢得的选票也更多。

这里有一个规则:随着观点熟悉程度和重要性的增加,被动说服的影响力会越来越小

媒介

需要承认的是,对于我们最主要的影响来自和他人的解除而不是传媒。课堂外的人际关系对学生的身心成熟有重要的影响。但这也不代表媒体对个人没有影响。卡茨(Katz,1957)观察到,多数媒体影响都通过沟通的两步流程,即从媒体到有影响力的人、再到普通群众。即使那些没看过电视的孩子,也会因为身边那些看过孩子的人间接受到电视广告的影响。

媒体越接近生活,信息就越具有说服力。即现场>录像>录音>文字。但是,文字形式的信息通常具有最好的理解和回忆效果。所以,当信息难以理解时,其说服效果按媒体排序,刚好是和上面相反的。因为信息较难时,文字媒介可以让接收者把握理解节奏,同时避免外周因素(环境音、画面)影响。

听众

自尊水平低的人理解信息慢,难以说服;自尊水平高的人理解信息后坚持己见,也难以说服。水平适中的人最易受到影响。

年龄

在听众年龄上,有两种理论,生命周期理论生活时代理论。研究者发现,从现象上看,实际情况更符合后者,即人们的态度较少随年龄发生变化。

十几、二十几岁的年轻人形成的态度会在长期内保持稳定。在此间的经历也可以给个体留下深刻和持久的印象,从而给人格定型。

他们在想什么

说服过程的关键不在信息主体,而在它激发个体的思维方式。

  • 预先警示会提升说服的困难,尤其是在听众积极参与观点时。他们会准备好抵御,面对可能出现的质疑。
  • 分心会减少辩驳,注意力被别的东西转移并阻碍反驳时,言语的说服效果会增加。尤其当信息简单时。下次可以在和对象吵架时试试这招。
  • 不积极参与的听众使用外周线索。思辨能力强和积极参与者的认知需求较强,喜欢走中心途径。而相反情况下,会走外周途径。当信息看起来无关紧要时,外周线索更为明显。

佩蒂、卡乔波等人通过多个实验研究发现了激发人们思考,引导人们走中心途径的方法:

  • 使用反问(老师很喜欢使用)
  • 使用多个演说者
  • 让人们感到自己有责任评价和传达信息
  • 使用放松的姿态
  • 重复信息以吸引人们的注意力

对于强有力的信息,激励思考可以增加说服力,并减小微弱信息的说服力

邪教如何洗脑

类似人民圣殿、统一教团、天堂之门等邪教利用了说服的一些特性蛊惑教众危害社会。

  • 态度决定行为。邪教领导者通过重复的行动准则和仪式让教众内化行为,成为有责任感的拥护者。在一开始利用登门槛效应逐步减少人们的抵抗
  • 增强说服力:一个具有个人魅力的领导者;生动、感性的信息;年轻的或处于人生转折点的听众。

团体效应

邪教组织通常会将成员和其原先的社会关系隔离开,达到“社会鼻塞”的程度。在只和组织内部的成员发生联系的情况下,逐步滋生偏执和妄想。同时,由于邪教切断了新成员和老朋友的联系,他们往往会感到孤立无援,从而花时间融入新团体。

狂热的自助组织也具有这样的特征,组织具有很强的凝聚力,有激进极端的思想,对成员的行为影响深刻。有意思的是,心理治疗的一些情境也和此类似:

  • 相互信任的社会支持
  • 专业知识和希望
  • 独特的信念和解释个体困难的新视角
  • 一系列的仪式和学习体验

如何抗拒说服

说服有好有坏,合适的技巧可以帮助我们避免令人生厌的说服。

加强个人承诺

温和的攻击人们的立场可以激励人们更相信自己的立场,比如在辩论赛中见到的那样。

  • 一方面,攻击强度不足以驳倒他人时,会激发人们以更极端的方式维护信仰的立场
  • 另一方面,我们会感到愤怒,且在驳倒他人时会获得较高的自我肯定感

态度免疫就是利用这一点实现的。先让人们接受观点的小小挑战作为预防针,会增加他们抵制更强烈攻击的能力。适当的辩论是抵制说服的最佳途径。态度免疫计划已经被应用在现实生活中,实验发现适当的挑战可以降低孩子的吸烟率和增强孩子对广告的抵抗能力。另外,态度免疫也可以用来抵制邪教和洗脑的不良影响。

另一方面,这也预示着,效果不佳的说服还不如没有。那些曾经拒绝过戒烟劝说的人更可能对以后的所有劝说具有抵抗力,因为效果不佳的劝说引起了他们的防御心理。

群体影响

群体影响个体,个体也会影响群体。

首先我们要明确群体的概念,对此,肖和特纳提出了两种判定方式:

  • 群体之间存在互动并会相互影响
  • 群体成员把群体内的人视作“我们”而不是“他们”

这一张主要考察群体的三种影响(社会助长社会懈怠去个性化),之后再讨论社会影响的三个典型例子(群体极化群体思维少数派影响)。

社会助长:他人在场影响

他人在场有些时候会提高人的作业水平(比如一些简单工作),有时候会降低人的水平(比如表演杂技)。对此扎伊翁科(Robert Zajonc)用一个简单的理论给出了解释:

他人在场引起唤起,唤起能够增强任何优势反应的倾向

所谓“优势”反应即大概率的反应。对于简单任务正确反应概率较高,唤起的是正确反应;对于复杂任务,唤起增强的是错误反应,因此他人在场时表现更差。

由于运动员所表现的都是熟练掌握的技能,从而有观众在场时,往往能激励人们的表现出最佳水平。这也是主场有优势的一定原因。

拥挤现象

他人的影响效应会随人数增加而递增。在完成有挑战性的任务时,众多支持者在场可能会引起个体做出更差的表现。

“处在人群中”对个体的积极和消极反应都会增强。人们坐得很近时,友善的人更受欢迎,而不友善的人更令人讨厌。我们坐得更近时,更容易注意到别人并融入他们的氛围之中。拥挤也会增强唤起状态,待在拥挤房间的被试心率更快、血压也更高。同样的学生人数,在拥挤环境下(小房间)上课比在宽松环境下(大房间)上课更活跃。

为何会有唤起

对于为何人们会产生唤起,有研究者发现了以下三个可能原因:

  • 评价顾忌,即人们认为受在场者的评价,这会激发并提高他们的优势反应。这也可以解释下面两个行为。另外受评引发的自我意识也会干扰我们熟练掌握的自动化行为。
    • 当与比自己优秀一些的人共事时,人们的表现更好
    • 高层领导的唤起状态会随着无关痛痒的人加入而降低
  • 分心,当我们注意他人和注意任务间存在矛盾时,认知系统负载过大,从而引起唤起
  • 纯粹在场,非人类动物身上的唤起现象也很普遍。只需要他人在场就会引起唤起作用。在许多写字楼中的开放式办公区意在利用他人在场增加个体的工作效率。

社会懈怠:减少努力

社会助长通常发生在人们为个人目标努力时。当大家在为共同目标努力,且个人努力难以衡量时,人们的努力会减少,即社会懈怠。

  • 6个人尽力叫喊的喧闹声不如1个人的3倍响
  • 团体拔河时,个体努力程度从只有个人单独时的一半

在群体条件下,人们会受到搭群体便车的诱惑,随着群体规模增大,个体付出的努力也在减少。这种现象在增强个体评价顾忌时有所减少。

  • 激励小组成员的一种方式是个体成绩可识别化
  • 集体公社时农民的工作效率远不如承包制下农民们的效率

在有些情况下,群体目标还是能起到激励的作用:

  • 群体目标极具吸引力,且需要每个人尽力去做
  • 任务具有挑战性、引人入胜的特点
  • 群体内认同度极高,如小组内都会好朋友而非陌生人
  • 维持群体较小的规模

集体主义下的社会懈怠要弱于个体主义社会,女性的社会懈怠也不如男性强烈

去个性化:失去自我

群体情境有时会使人失去自我觉知能力,导致丧失自我和自我约束(正如《乌合之众》中指出的)。

群体能引发人们的唤起状态,当群体能扩散和分摊责任时,常规的约束就会变小,人们的行为就会从普通的失态到社会暴力。另外,群体可以产生出一种兴奋感,这种比个体更强大的力量对个体很有吸引力,在某些情境下,人们可以抛弃道德约束、忘却个人身份,顺从社会规则,即去个性化。

群体在引起唤起时,还在某种程度使个体身份模糊化,当人群规模较小且曝于日光下时,往往不至那么狂热。群体规模越大,成员越有可能失去较多自我意识,而受群体意识裹挟。这种匿名性还会引起更激进的行动。

  • 网络的匿名性使得激进型言论更多
  • 大部分孩子因群体掩盖、匿名性(未被人发现)去个性化时,会更出格
  • 匿名的袭击者表现出更严重的袭击行为
  • 遮掩个人特征的身体彩绘会让部落斗士更暴力

对此要指出,群体的去个性化不光只有负面作用,匿名性使人们自我意识减弱,群体意识增强,更容易对情境线索和暗示做出回应,不论线索是消极还是积极的。

群体表现出攻击性之前常常会发生引发人们唤起和分散注意力的事件,如合唱、跳舞。当人们看到别人和自己做出同样行为时,会对自己做出冲动性的举动产生自我强化的愉悦感。有些时候,我们甚至会主动寻找去个性化的群体体验:跳广场舞、群体交流、快闪等。我们会从中体验强烈的积极情感和与他人亲密无间的关系。

自我意识弱化

自我意识淡薄、去个性化的人更不自控,更可能不顾及价值观就行动,对情境的反应也更强烈。相反,自我意识强烈的人更不易受环境影响。

自我觉察是去个性化的对立面,在镜子前或摄像机前的人,会表现得更加自控,从而在情境中表现出更高的言行一致性。

喝酒等情境会降低个体的自我觉察;镜子、摄像机、明亮的光线、很大的姓名标签、沉思、个性化着装、家等情境都可以降低个体的去个性化。父母在孩子参加聚会时,也都会说:“玩得开心,还有要记住你的身份”。

享受欢乐时,保持自我觉察,保持自我个性不被去个性化。

群体极化:观点强化

群体讨论通常会强化成员最初的观点。群体观点有时更冒险,有时则更谨慎。

日常生活有许多群体极化的例子:

  • 男孩群体和女孩群体的性别隔离会加强他们最初的中度性别差异
  • 大学生群体间的差异会随着时间推移扩大化
  • 社区内,想法相似的人会渐渐聚集起来,并使它们共有的倾向加强。犯罪团伙就是这样形成的。
  • 恐怖组织就是从拥有相同不满情绪的人聚集起来开始形成的
  • 媒介的增多和社会分隔使人们更容易与相同目的的人集合在一起,现今的互联网会加速这个过程

对于极化有两种解释:一种是信息影响,群体中个人的观点也会加入讨论,互相补充,互相激励,在相似的观点不断重复后,人们就会逐渐认同这些观点;另一种是规范影响社会比较理论提出,人类希望能对自己的能力和观点进行评价,需要通过和他人比较来达成,我们经常被“参照群体”的人们说服。在发现身边的人意见都与自己类似时,我们会表现得比以前的意向更胜一筹。(上面的解释很像从众中的两种解释原因)。在规范影响的形成过程中,会有一个人众无知的阶段,即人们不清楚他人的观点都害怕迈出第一步。社会比较会影响价值判断(他是个什么样的人),对事实判断影响则较弱(他做了什么事)。

群体思维:好决策&坏决策

人们为了维护群体和睦而压制异议,即群体思维。友善的、有凝聚力的、有一个支配性领导的群体较容易拥有群体思维。群体思维会高估群体的力量和权利

  • 无懈可击的错觉,如珍珠港事件
  • 对群体道义无可置疑
  • 合理化群体决策,即使决策时发动与他国的战争
  • 对对手的刻板印象

群体也会追求一致性

  • 从众压力,群体成员会抵制对群体设想、计划提出猜疑的人
  • 自我审查,即压制自己的疑虑
  • 一致同意错觉
  • 心理防御,群体成员为了保护群体,让质疑群体的信息不对群体造成干扰

群体思维很容易导致错误的决策。也不是所有群体都会滋生群体思维,安全而团结的群体会为成员提供自由氛围来提出异议。另外,也有措施可以避免群体思维:

  • 公平,不偏向任何立场
  • 鼓励批判性的分析
  • 将群体拆分小组,再重组讨论不同意见
  • 欢迎局外人的批评和意见

头脑风暴也利用上面的方法激发创造性的想法。在运用中,人们发现庞大团体的头脑风暴是低效的。先进行群体头脑风暴再进行个人头脑风暴、让小组成员通过书写交流可以促进群体头脑风暴。

少数派影响

个体并不是完全受群体影响,也能反过来影响群体。正如从众、说服一样。少数派受以下几个重要因素影响:

  • 一致性,坚定自我立场的少数派更有影响力。尽管这个过程会比较痛苦
  • 自信,明显的自我支持会促使多数派重新考虑他们的立场
  • 从多数派叛离,从多数派投奔来的少数派更具说服力,并可能产生滚雪球效应

领导分两种类型:任务型社会型。任务型领导具有支配性,可以睿智地发出命令很好地完成工作;社会型领导通常具有民主风格,可以接纳团队成员的意见。如果有机会在决策中发言,人们会对决策结果表现更积极,因此看重群体感受并为成就感到骄傲的人会在民主领导下蓬勃发展。

具有领导气质的人大多是外向的、充满活力的、正直的、易于相处的、情绪稳定和自信的个体。他们通常可以赢得信任,并鼓舞其他人追随自己。

Visual Studio Code Tips and Tricks

这里介绍一些能提高VS Code产率的方法。

基础

欢迎页右下角提供Interactive playground(在命令面板里的Help > Interactive Playground)。里面提供了VS Code一些关键特性的快速介绍。比如:

  • 多光标编辑
  • 行操作
    • 整行向上、下移动Option + up/down
    • 整行向上、下复制Shift + Option + up/down
    • 删除整行Shift + Cmd + K
    • 注释整行Cmd + /
  • 重构
    • 重命名:光标处F2,修改后自动同步相关位置
    • 选中语句 -> Cmd + . -> 选择重构方式
  • 格式化文档:Cmd K + Cmd F或者Shift + Option + F
  • 折叠
    • 折叠Option + Cmd + [,展开Option + Cmd + ]
    • 折叠所有Cmd K + Cmd 0,展开所有Cmd K + Cmd J
  • 代码片段:见代码片段一节
  • Emmet:见Emmet一节

常见操作

指令面板

快捷键Shift + Cmd + P。里面的常见命令都有快捷键提示。

快速打开文件

快捷键Cmd + P。点击打开文件,点击右方向键打开不会关闭当前面板。

状态栏

Shift + Cmd + M

修改语言

Cmd K + M

命令行工具

打开指令面板,安装code命令。

常见命令:

1
2
3
4
5
6
7
8
9
10
11
# open code with current directory
code .

# open the current directory in the most recently used code window
code -r .

# change the language
code --locale=es

# open diff editor
code --diff <file1> <file2>

自定义编辑器

Cmd + ,,打开编辑器编辑配置。也可以

插件

Cmd + Shift + X,利用插件增强你的开发体验和生产率。

文件及目录

  • 内置terminal,Ctrl + `或View -> Terminal或命令面板里输入View: Toggle integrated terminal。深度阅读
  • 自动保存,setting.json中设置"files.autoSave": "afterDelay"
  • toggle侧边栏,Cmd + B
  • 专注模式,Cmd K + Z
  • 分栏,Cmd + \,使用Cmd + 1, Cmd + 2等切换
  • 关闭当前tab,Cmd + W
  • 浏览历史
    • 全部历史,Ctrl + Tab按住选择
    • 回退,Ctrl + -
    • 前进,Ctrl + Shift + -

高效编辑

多光标

  • Cmd + Click可以多光标操作
  • Cmd + Shift + L可以在所有选中单词的末尾多光标操作
  • Cmd + D选中当前单词

盒式选中

Shift + Alt + 拖拽

选中当前行

Cmd + L

快速滚动

Alt + 滚动可以达到x5速度的滚动

行操作

行向上/下复制/剪切,见[基础]一节里的介绍

层级选中

Ctrl + Shift + Cmd + left/right可以扩大/缩小当前选择范围

Goto Symbol

  • Shift + Cmd + O当前文件下选择符号名,输入@:可以进行分类
  • Shift + Cmd + T当前工作区下选择符号名

Goto特定行

Ctrl + G

trim行尾空格

Cmd K + Cmd X

Markdown预览

  • Cmd + Shift + V
  • 实时预览Cmd K + V

代码联想

  • 查看定义,F12Option + Click
    • 查看定义(不切换上下文)Option + F12
  • 查看引用(不切换上下文),Shift + F12,查看整个项目引用Shift + Option + F12
  • 重命名,F2
  • 搜索替换,Cmd + FCmd + Shift + F

Emmet

VS Code支持Emmet风格书写HTML代码。完整的Emmet语法参考这里

Code Snippet

Code Snippet即能让你更容易复用的代码模板,如for循环,if语句等。在代码联想时,可以自动帮你补全,开启"editor.tabCompletion": "on"配置时,也可以使用Tab键补全。

在VS Code Marketplace中有许多snippets拓展。搜索”xxx snippet”多半你能找到已有的snippet拓展。

书写自己的snippet可以参考官方文档

Git集成

Shift + Ctrl + G打开。

  • 支持Side by side和Inline view两种diff模式。
  • 左下角快捷切换分支
  • 手动添加文件、解决冲突

更多参考Git integration一节。

调试

在命令面板中输入”Debug”查看相关命令。更多查看Debugging一节

脚本任务

参考Task Runner

尽管项目中已经用上了TypeScript,但是主要场景下对TS的高级特性设计较少,再看过leetcode面试题后,觉得自己的了解程度还远远不够。于是参考《TypeScript Deep Dive》这本开源书(中文版)开始再学习

TypeScript Playground: http://www.typescriptlang.org/play/

TypeScript项目

编译

TS的编译过程主要通过tsconfig.json文件来配置(当然你也可以通过命令行的方式指定)。TS有些自己的默认配置,你也可以在complierOptions下自定义你的配置。

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
{
"compilerOptions": {
/* 基本选项 */
"target": "es5", // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
"module": "commonjs", // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
"lib": [], // 指定要包含在编译中的库文件
"allowJs": true, // 允许编译 javascript 文件
"checkJs": true, // 报告 javascript 文件中的错误
"jsx": "preserve", // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
"declaration": true, // 生成相应的 '.d.ts' 文件
"sourceMap": true, // 生成相应的 '.map' 文件
"outFile": "./", // 将输出文件合并为一个文件
"outDir": "./", // 指定输出目录
"rootDir": "./", // 用来控制输出目录结构 --outDir.
"removeComments": true, // 删除编译后的所有的注释
"noEmit": true, // 不生成输出文件
"importHelpers": true, // 从 tslib 导入辅助工具函数
"isolatedModules": true, // 将每个文件做为单独的模块 (与 'ts.transpileModule' 类似).

/* 严格的类型检查选项 */
"strict": true, // 启用所有严格类型检查选项
"noImplicitAny": true, // 在表达式和声明上有隐含的 any类型时报错
"strictNullChecks": true, // 启用严格的 null 检查
"noImplicitThis": true, // 当 this 表达式值为 any 类型的时候,生成一个错误
"alwaysStrict": true, // 以严格模式检查每个模块,并在每个文件里加入 'use strict'

/* 额外的检查 */
"noUnusedLocals": true, // 有未使用的变量时,抛出错误
"noUnusedParameters": true, // 有未使用的参数时,抛出错误
"noImplicitReturns": true, // 并不是所有函数里的代码都有返回值时,抛出错误
"noFallthroughCasesInSwitch": true, // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)

/* 模块解析选项 */
"moduleResolution": "node", // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
"baseUrl": "./", // 用于解析非相对模块名称的基目录
"paths": {}, // 模块名到基于 baseUrl 的路径映射的列表
"rootDirs": [], // 根文件夹列表,其组合内容表示项目运行时的结构内容
"typeRoots": [], // 包含类型声明的文件列表
"types": [], // 需要包含的类型声明文件名列表
"allowSyntheticDefaultImports": true, // 允许从没有设置默认导出的模块中默认导入。

/* Source Map Options */
"sourceRoot": "./", // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
"mapRoot": "./", // 指定调试器应该找到映射文件而不是生成文件的位置
"inlineSourceMap": true, // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
"inlineSources": true, // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性

/* 其他选项 */
"experimentalDecorators": true, // 启用装饰器
"emitDecoratorMetadata": true // 为装饰器提供元数据的支持
}
}

TS有几种不同的编译方式:

  • 运行tsc,自动定位当前目录下tsconfig.json
  • 运行tsc -p [your path],手动指定config路径
  • 运行tsc -w进入观测模式,在文件更改时自动重新编译

你可以通过不同方式指定要编译的文件:

  • files直接指定要编译的文件
  • include指定包含的文件
  • exclude指定排除的文件

配置值可以是glob格式。

声明空间

TypeScript中有两种声明空间:类型声明空间变量声明空间。前者只能用作类型注解,后者可以用来当做变量使用。

文件模块

TS中有多种模块系统选项:

  • AMD:仅在浏览器端使用
  • SystemJS:已被ES模块替代
  • ES模块:当前的支持有限
  • CommonJS:当前比较好的一个选择

一般在工程中使用ES模块语法,模块选项使用CommonJS。TS中对类型也可以同样适用import和export。

路径

通常情况由moduleResolution选项指定。这个选项在tsconfig.json中声明。在声明module: commonjs时,moduleResolution自动指定为node

导入路径分两种:

  • 相对路径,使用./或是../与文件、文件夹名称组成
  • 动态路径,TS模块解析将会模仿Node模块解析规则,即去当前目录、所有父目录的node_modules下寻找对应路径模块

如果你本身对node下的模块查找很熟悉,那么恭喜,你已经掌握了TS的模块查找。

global.d.ts

在项目中可以通过declare module 'somepath' {}的方式声明一个全局模块,这样的一个global.d.ts是声明全局类型的好地方。从js迁移到ts的项目通常需要一个这样的声明

命名空间

TypeScript下可以使用namespace拆分变量的命名空间。

1
2
3
4
5
6
7
8
9
10
11
12
namespace Logger {
export function log(msg) {
console.log(msg);
}
export function error(msg) {
console.error(msg);
}
}

// usage
Logger.log('A message');
Logger.error('An error');

namespace支持嵌套定义,在快速演示移植旧的JavaScript

动态导入

在使用ES的动态导入功能时,为了保证TS在转换代码时保留import()语句,tsconfig.json中的module需要是esnext

类型系统

概览

基本注解

包括JS的原始类型

  • string
  • number
  • boolean
  • object
  • 其他基本类型

数组类型在元素类型后追加[]实现。键值对类型使用{[key: string]: any}实现。

你可以使用interface封装自己的类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
interface User {
name: string;
age: number;
school: {
name: string;
location: string;
postcode: string;
},
tags: {
id: string;
name: string;
}[]
}

另外,对于临时的类型需要。可以直接使用内联的方式注解类型。

1
2
3
4
5
6
7
const user: {
name: string;
title: string;
} = {
name: 'foo',
title: 'CEO'
}

特殊类型

除了上面的基本类型,还有一些常用的特殊类型。

any意味着任意类型,any类型可以兼容任何TypeScript中的类型。因此:

  • 任意类型都可以赋值给any
  • any也可以赋值给任意类型

初学者在从JavaScript迁移到TypeScript时,通常要借助any的帮助。但实际上使用any就代表告诉TypeScript编译器不要进行任何类型检查。

TypeScript 3.0特性中,出现了和any类似的unknown关键字。但是后者是type safe的.

  • 任何类型都可以赋值给unknown
  • unknown在类型检查后才能赋值给任意类型

另外在设置编译属性strictNullChecksfalse时,字面量nullundefined也可以赋值给任意类型变量。

void用来表示一个函数没有返回值,never表示不会发生的类型。例如抛出错误的函数、死循环函数的返回值类型、以及字面量空数组的元素类型。

1
2
let emptyArr = []; // never[]
let func: never = (() => throw Error('Throw an error'));

never类型间可以相互赋值,但不能和其他类型相互赋值。

泛型

计算机算法在对封装类型操作时,往往不依赖于元素类型,这种情况下使用泛型描述,TypeScript会帮助推断元素类型,保证类型安全。

1
2
3
4
5
6
function reverse<T>(items: T[]): T[] {
// ...
}
const res1 = reverse([1, 2, 3]);
res1[0] = '1'; // Error
res1[1] = 2; // ok

高级类型

交叉类型,写作A & B,表示同时具有AB两种类型的属性,新类型的对象可以使用A或者B的功能。

联合类型,写作A | B,表示是AB其中一种类型,较常用在入参的内联描述中。

1
2
3
4
5
6
7
8
9
10
11
function extend<T, U>(first: T, second: U): T & U {
let res: <T & U> = {};
return {
...first,
...second
};
}

function batchOperate(id: string | string[]) {
operate([].concat(id));
}

元组类型,这不是一种新类型,它用来描述不同类型元素的集合,就像宽容的JS数组一样。

1
2
3
type user = [string, number];
const userInfo: user = ['John', 32];
const [userName, age] = userInfo;

类型别名

除开interface,还有type可以更快捷地定义类型别名。在结合上述高级属性使用时,类型别名type会是不错的选择。对比interfacetype:

  • 使用interface定义基本的层级结构,它可以和implements以及extends配合使用
  • 在需要的类型不需要从头构造,而是从已有类型推导出来时,使用type,它更像是给这些computed type一个语义化的名字

枚举

枚举是常见的组织互斥的一组常量的方式。TypeScript中用enum关键字表示。默认的枚举是数字类型的,即使用数字作为索引值;

1
2
3
4
5
6
7
8
9
enum Color {
Red,
Green,
Blue
}

let col = Color.Red; // 0
const anotherCol = Color[0]; // 'Red'
col = 0 // ok

在使用数字类型时,枚举值可以用数字代替。默认情况下,枚举值从0开始,当然可以用 = 1修改默认的枚举值。下面有一个枚举值和标记的组合用法。

1
2
3
4
5
6
7
8
9
10
11
12
enum AnimalFlags {
None = 0,
HasClaws = 1 << 0,
CanFly = 1 << 1,
EatsFish = 1 << 2,
Endangered = 1 << 3
}

interface Animal {
flags: AnimalFlags;
[key: string]: any;
}

在不同的Animalflags做位运算时可以非常方便地完成布尔代数的一些操作。

另外,枚举类型的值可以通过赋值成为字符串类型。在使用常量枚举时,TypeScript会将所有出现枚举的位置都替换成内联的常量,而不需要查找枚举变量,从而提高性能提升。

从JavaScript中迁移

总的来说有下面几步:

Step1:添加tsconfig.json文件。

Step2:修改文件拓展名为ts,使用any避免干扰你主要工作的报错,记得在之后规范

Step3:写新的TypeScript代码,减少any使用

Step4:回头为你的老代码添加类型

Step5:为你的第三方库引用类型声明,绝大多数优秀的JS库都已经有人帮忙写好类型声明

Step6:对于那些没有声明的第三方库,需要你自己书写类型声明或者declare module yourmodule一劳永逸

上面提到的类型声明,即DefinitelyTyped通过npm包的方式引入,包有固定前缀@types

有些类型声明的引入会带来全局scope的定义,可以通过在tsconfig.json里配置types来限制引入的声明文件

1
2
3
4
5
6
7
{
"compilerOptions": {
"types" : [
"jquery"
]
}
}

类型声明文件

通过declare关键字告诉TypeScript,你正在表述其他位置已经存在的全局变量。强烈建议把所有的声都放在以.d.ts结尾的文件名的文件内。环境声明不会被编译成代码。

在这样的模块、变量、类型声明文件里,interface是最常见的。用户代码中可以用类实现这些接口。但是请记住,interface旨在声明JavaScript中可能存在的数据结构。

1
2
3
4
5
6
7
8
9
10
11
interface Point {
x: number;
y: number;
z: number; // New member
}

class MyPoint implements Point {
// ERROR : missing member `z`
x: number;
y: number;
}

lib.d.ts

为了便于你能快速开始书写类型检查的代码,TypeScript自带了BOM的变量声明(包含window、document、math等)位于lib.d.ts中。你可以在你的项目下添加global.d.ts,对已有的全局变量做自己的拓展。

1
2
3
4
5
6
7
8
interface Window {
foo(): void;
}
interface DateConstructor {
lastDay(): number;
}
window.foo();
Date.lastDay()

你在自己定义的global.d.ts中可以通过拓展global,修改全局空间内的类型定义。

1
2
3
4
5
declare global {
interface String {
endsWith(suffix: string): boolean;
}
}

编译选项

  • 指定--noLib可以排除TypeScript自动引入的lib.d.ts,这通常出现在
    • 运行JavaScript的环境和标准浏览器相距甚远
    • 你希望严格控制全局变量的使用
  • 指定--lib可以对编译环境进行细粒度控制引入的包类型
    • tsc中,tsc --target es5 --lib dom,es6
    • 也可以在tsconfig.json中声明
      1
      2
      3
      "compilerOptions": {
      "lib": ["dom", "es6"]
      }

如果没有指定--lib,TypeScript会根据当前编译选项中的target导入默认库。

  • --target为es5时,导入es5、dom、scriptdom
  • --target为es6时,导入es6、dom、dom.iterable、scripthost

函数

函数注解可以使用内联或interface的方式。通常编译器可以根据代码自动推断函数的返回类型。函数入参的可选参数通过类型注解前的?说明。另外,TypeScript允许你声明函数重载,注意,这里只是声明,重载需要自己实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function adult(itself: human);
function adult(itself: human, mate: human, children: human[]);
function adult(itself: human, mate?: human, children?: human[]) {
if (!mate) {
return { itself }
}
return {
itself,
mate,
children: children || []
};
}

adult(yourself, anotherGuy); // Error

可调用的

可以用类型别名或接口表示可调用的类型。函数重载和构造函数定义都可以在其中实现。使用new作为前缀后,需要使用new关键字去调用这个函数。

1
2
3
4
5
6
7
8
interface Overloaded {
(foo: number) => void;
(foo: string) => number;
}

interface ConstructorFunc {
new (): string;
}

除此之外,还可以使用箭头函数作内联函数注解,但这种时候无法表示重载。

1
const foo: (bar: number) => string = bar => bar.toString();

字面量类型

字面量 + 联合类型构成TS中常用的字面量类型。

1
2
3
type Seasons = 'spring' | 'summer' | 'autumn' | 'winter';
type binary = 0 | 1;
type bools = true | false;

很多时候字面量类型会通过keyof一个键值对的形式来构造。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 用于创建字符串列表映射至 `K: V` 的函数
function strEnum<T extends string>(o: Array<T>): { [K in T]: K } {
return o.reduce((res, key) => {
res[key] = key;
return res;
}, Object.create(null));
}

// 创建 K: V
const Direction = strEnum(['North', 'South', 'East', 'West']);

// 创建一个类型
type Direction = keyof typeof Direction;

类型断言

TypeScript有自己的类型推断,但是允许你使用类型断言去覆盖。通过as Something<Something>的方式。但是后者接近JSX语法,所以更多使用前者。

断言是编译时的,为编译器提供分析代码的方法。TypeScript在进行类型断言时,会判断源类型S是否是目标类型T的子集,若不是则不能成功断言。

类型保护

使用JS中typeofinstanceof可以帮助TypeScript推导出条件语句内的变量类型。使用in操作符,也可以帮助TypeScript判断类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface A {
x: number;
}

interface B {
y: string;
}

function doStuff(q: A | B) {
if ('x' in q) {
// q: A
} else {
// q: B
}
}

在联合类型中,如果有类型使用字面量,TypeScript甚至可以通过判断字面量确定变量类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type Foo = {
kind: 'foo'; // 字面量类型
foo: number;
};

type Bar = {
kind: 'bar'; // 字面量类型
bar: number;
};

function doStuff(arg: Foo | Bar) {
if (arg.kind === 'foo') {
console.log(arg.foo); // ok
console.log(arg.bar); // Error
} else {
// 一定是 Bar
console.log(arg.foo); // Error
console.log(arg.bar); // ok
}
}

最后,弥补JS中plain object没有instanceoftypeof自我检查的漏洞。TypeScript提供了is允许自定义类型判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 仅仅是一个 interface
interface Foo {
foo: number;
common: string;
}

interface Bar {
bar: number;
common: string;
}

// 用户自己定义的类型保护!
function isFoo(arg: Foo | Bar): arg is Foo {
return (arg as Foo).foo !== undefined;
}

类型推断

TypeScript可以根据一些规则推断出变量类型:

  • 定义变量
  • 函数返回
  • 赋值
  • 结构化(数组元素、对象属性)
  • 解构

在推断不出类型或使用第三方JS库时,类型会被判定为any。开启编译选项noImplicitAny可以避免这种问题。

类型兼容

  • 结构化:只要对象结构匹配,名称无关紧要
  • 多态性:子类实例可以复制给基类实例,相反则不行
  • 函数
    • 返回类型:数据较多的可以赋值给数据较少的
    • 入参:入参较少的可以赋值给入参较多的
    • 可选参数、Rest参数:可以相互赋值(可选和必选仅在strictNullChecks选中时相互兼容)
    • 入参类型:父类子类相互兼容(牺牲安全性确保便利性)
  • 枚举:和数字类型兼容、不同枚举间不兼容
  • 类:仅比较实例成员和实例方法,不比较构造函数和静态成员,privateprotected成员必须来自相同的类
  • 泛型:泛型对兼容性没有影响(这可能会带来一些潜在问题)
1
2
3
4
5
6
7
8
9
10
interface Poin2D {
x: number;
y: number;
}

let iTakePoint2D = (point: Point2D) => {};
let iTakePoint3D = (point: Point3D) => {};

iTakePoint3D = iTakePoint2D; // ok, 这是合理的
iTakePoint2D = iTakePoint3D; // also ok,为什么?

readonly

readonly标记接口属性,表示预期不可修改。获取使用Readonly封装一个泛型T,表示泛型内的属性均不可修改。同样地,你可以为索引签名声明readonly,表示所有索引元素均不可修改。还有些情况下,如果属性配置了getter,但没有setter也会被认为是只读的。

1
2
3
interface Foo {
readonly [x: number]: number;
}

readonlyconst的主要不同在于,前者用来修改属性,后者用于变量。

索引签名

索引即数组或键值对的索引。TypeScript中索引类型只能是stringnumber类型。这意味着,也可以使用字面量类型作为索引类型。

1
2
3
4
type Index = 'a' | 'b' | 'c';
type FromIndex = { [k in Index]?: number };

const good: FromIndex = { b: 1, c: 2 };

在一些特殊场景下,可以同时支持stringnumber类型。

1
2
3
4
interface ArrStr {
[key: string]: string | number; // 必须包括所用成员类型
[index: number]: string; // 字符串索引类型的子级
}

流动的类型

typeof可以捕获变量、类成员类型。使用typeof在捕获一个字符串字面量时,得到的类型是字面量类型。

1
2
3
4
5
6
7
8
9
10
11
12
let foo = 123;
let bar: typeof foo; // 'bar' 类型与 'foo' 类型相同(在这里是: 'number')

// 捕获字符串的类型与值
const faz = 'Hello World';

// 使用一个捕获的类型
let baz: typeof faz;

// bar 仅能被赋值 'Hello World'
baz = 'Hello World'; // ok
baz = 'anything else'; // Error

使用keyof捕获一个类型的键。

ThisType

在对象字面量方法的类型定义上声明ThisType()可以修改发放内this的类型,这常被用在this值被重新绑定的情况。

Tips

bind的隐患

lib.d.ts中,对bind的定义如下:

1
bind(thisArg: any, ...argArray: any[]): any

由于返回值是any类型,意味着bind返回的函数将失去类型检查(最新的TS 3.2已经优化了这个问题)。

柯里化函数

用一系列箭头表示。

1
2
3
4
5
6
7
8
9
10
11
// 一个柯里化函数
let add = (x: number) => (y: number) => x + y;

// 简单使用
add(123)(456);

// 部分应用
let add123 = add(123);

// fully apply the function
add123(456);

一些建议

  • 使用继承而不是as来实现泛型实例化
  • 使用as来初始化对象字面量的空对象
  • 尝试使用类组织代码
  • 小心使用setter,不要牺牲代码可读性
  • 在参数名可以很好提高可读性、入参很多时,考虑让函数接受一个对象参数

Reflect Metadata

Reflect Metadata是ES7的提案,用于在声明时添加和读取元数据。Reflect Metadata的API可以用于类或类属性上,

1
2
3
4
5
6
7
8
9
10
@Reflect.metadata('inClass', 'A')
class Test {
@Reflect.metadata('inMethod', 'B')
public hello(): string {
return 'hello world';
}
}

console.log(Reflect.getMetadata('inClass', Test)); // 'A'
console.log(Reflect.getMetadata('inMethod', new Test(), 'hello')); // 'B'

因此可以通过Reflect.getMetadata的API来获取类相关的元数据。

自定义metadatakey

可以定义自己的reflect metadata。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function classDecorator(): ClassDecorator {
return target => {
// 在类上定义元数据,key 为 `classMetaData`,value 为 `a`
Reflect.defineMetadata('classMetaData', 'a', target);
};
}

function methodDecorator(): MethodDecorator {
return (target, key, descriptor) => {
// 在类的原型属性 'someMethod' 上定义元数据,key 为 `methodMetaData`,value 为 `b`
Reflect.defineMetadata('methodMetaData', 'b', target, key);
};
}

@classDecorator()
class SomeClass {
@methodDecorator()
someMethod() {}
}

Reflect.getMetadata('classMetaData', SomeClass); // 'a'
Reflect.getMetadata('methodMetaData', new SomeClass(), 'someMethod'); // 'b'

可以借助Reflect Metadata的这个特点,实现诸如控制反转、依赖注入、装饰器等功能。

条件类型

1
T extends U ? X: Y

TypeScript 2.8的一个PR里第一次提到条件类型。条件类型主要规则如下:

  • 上式表示T如果可以赋值给U,返回类型X,否则返回Y
  • U中出现infer时,TypeScript会去推断infer后的类型变量(假设是V),如果V出在协变位置,则返回V所有可能性的联合类型,如果V出现在逆变位置,则返回V所有可能性的交叉类型(参考:协变与逆变

分布条件类型

在检查类型(extends前的类型参数)是原始类型(即没有被泛型等封装)时,称为分布条件类型(Distributive conditional types)。在实例化为实际类型时,联合类型会被拆分开。

例如,T实例化为A | B | C时,T extends U ? X : Y会被解析成(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T];
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;

type NonFunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T];
type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;

interface Part {
id: number;
name: string;
subparts: Part[];
updatePart(newName: string): void;
}

type T40 = FunctionPropertyNames<Part>; // "updatePart"
type T41 = NonFunctionPropertyNames<Part>; // "id" | "name" | "subparts"
type T42 = FunctionProperties<Part>; // { updatePart(newName: string): void }
type T43 = NonFunctionProperties<Part>; // { id: number, name: string, subparts: Part[] }

infer

如上文所说,infer最初出现是用来表示extends条件语句中待推断的类型。下文中若T满足(param: infer P) => any类型,则推出P类型。

1
type ParamType<T> = T extends (param: infer P) => any ? P : T;

infer有下面一些常规使用场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 获取返回值
type ReturnType<T> = T extends (...args: any[]) => infer P ? P : any;

// 获取构造函数的入参或实例类型
type Constructor = new (...args: any[]) => any;

// 获取参数类型
type ConstructorParameters<T extends new (...args: any[]) => any> = T extends new (...args: infer P) => any
? P
: never;

// 获取实例类型
type InstanceType<T extends new (...args: any[]) => any> = T extends new (...args: any[]) => infer R ? R : any;

联合infer和分布条件类型,可以实现一些骚操作,如tuple、intersection、union之间的转换。

1
2
type tupleToIntersection<T> = T[number];
type unionToIntersection<T> = (T extends any ? (k: T) => void : never) extends ((k: infer R) => void) ? R : never

如原文列的LeetCode TypeScript面试题。借助强大的条件类型和infer就能实现。

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
interface Action<T> {
payload?: T;
type: string;
}

// 预期的类型
type Result = {
asyncMethod<T, U>(input: T): Action<U>;
syncMethod<T, U>(action: T): Action<U>;
}

interface Module {
count: number;
message: string;
asyncMethod<T, U>(input: Promise<T>): Promise<Action<U>>;
syncMethod<T, U>(action: Action<T>): Action<U>;
}

type FuncNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never
}[keyof T]
type FuncProperties<T> = Pick<T, FuncNames<T>>;

type UnPackedParams<T> = T extends Promise<infer R> ? R :
T extends Action<infer R> ? R : T;
type UnPackedReturn<T> = T extends Promise<infer R> ? R : T;
type UnPackedFunction<T> = T extends (params: infer U) => infer R ? (params: UnPackedParams<U>) => UnPackedReturn<R> : never;
type Resolve<T> = {
[K in keyof T]: UnPackedFunction<T[K]>
}

// 修改 Connect 的类型,让 connected 的类型变成预期的类型
type Connect = (module: Module) => Resolve<FuncProperties<Module>>;

TypeScript编译原理

这部分内容较为简要。

编译器源文件位于src/compiler下,主要由以下部分组成:

  • 扫描器 Scanner
  • 解析器 Parser
  • 绑定器 Binder
  • 检查器 Checker
  • 发射器 Emitter

处理流程分下面几步:

  • Source --扫描器--> Token流
  • Token流 --解析器--> AST(抽象语法树)
  • AST --绑定器--> Symbols
  • AST + 符号 --检查器--> 类型验证
  • AST + 检查器 --发射器--> JavaScript代码

重要文件

  • core.ts TypeScript编译器使用的核心工具集
  • types.ts 包含整个编译器使用的关键数据结构和接口
  • system.ts 控制编译器和操作系统的所有交互

程序与抽象语法树

这里的“程序”指一个“编译上下文”。它包含SourceFile和编译选项。TypeScript有API获取SourceFile列表,每个SourceFile都是一棵抽象语法树的根节点。

节点

节点(Node)是AST的基本组成单位。Node有一些关键成员:

  • TextRange 标识节点在源文件的起止位置
  • parent?: Node 标识节点在AST中的父节点
  • 标志(flags)和修饰符(modifier)等有助于节点遍历的成员

下面有一些常用工具函数的用法:

  • ts.forEachChild 用来访问任一节点的所有子节点。这个函数会根据每个节点的node.kind判断node类型,然后再在子节点上调用cbNode。
  • ts.SyntaxKind是一个节点类型的常量枚举,用以表示不同的语法树节点
  • ts.getLeadingCommentRangests.getTrailingCommentRanges分别获取给定位置第一个换行符到token和之前的注释范围。
  • ts.getStartts.getFullStart分别获取一个token文本开始位置和上一个重要token开始扫描的位置
1
2
3
4
5
6
export const enum SyntaxKind {
Unknown,
EndOfFileToken,
SingleLineCommentTrivia,
...
}

扫描器与解析器

扫描器用于读取文本,并转换为Token流。扫描器由解析器(parser.ts)创建,为了避免创建扫描器的开销。parser.ts创建的扫描器是单例。

扫描器scanner.ts本身提供API给出扫描过程中的各种信息。尽管解析器创建的扫描器是单例,你仍可以使用createScanner创建自己的扫描器,并调用setTextsetTextPos随意扫描文件的不同位置。

解析器由程序经由CompilerHost创建,CompileHost通过getSourceFile准备好待编译文件,再交由解析器处理。解析器根据内部扫描器得到的Token构造一个SourceFile下的语法树。

解析器使用parseSourceFileWorkerparseStatements创建根节点和其余节点。具体解析每种节点的过程写在parseXxx中。

绑定器

绑定器主要职责是创建符号(Symbol)。符号将AST的声明节点和其他声明连接到相同实体上。绑定器会在检查器内被调用,检查器又被程序调用。

绑定器有几个重要函数:

  • bindSourceFile,检查file.locals是否定义,没有则交给内部函数bind处理。bindSourceFile内部还定义了许多别的内部变量,通过闭包被其他内部函数使用
  • bind处理任意节点绑定,先分配node.parent,在交给bindWorker做主要工作,之后调用bindChildren执行子节点的绑定
  • bindWorker根据节点类型,委托工作给特定的bindXXX函数完成。在bindXXX内最常用的是createSymbol函数
1
2
3
4
function createSymbol(flags: SymbolFlags, name: string): Symbol {
symbolCount++;
return new Symbol(flags, name);
}

绑定器会调用addDeclarationToSymbol绑定一个节点到符号,并把节点添加成符号的一个声明。声明就是一个有可选名字的节点。

检查器与发射器

检查器由程序初始化。在发射器中,类型检查在getDiagnostics中发生,函数被调用时会返回一个EmitResolver。这是一个createTypeChecker的本地函数集合。

TypeScript有两个发射器,emitter.ts完成TS到JavaScript,declarationEmitter.ts.ts创建声明文件(.d.ts)。

程序(Program)通过emit函数,把工作委托给emitter.tsemitFiles函数。emitFiles中借助emitJavaScript完成主要工作,

emitJavaScript中有大量内部函数,之后借给emitSourceFile发射文本,该函数设置currentSourceFile后交给本地的emit函数处理。在emitJavaScriptWorker中会根据不同符号类型调用不同发射器处理。在emitJavaScript的过程中,initializeEmitterWithSourceMaps使用带有sourceMap的版本覆盖部分本地函数,使大多数发射器代码无需考虑SourceMap。

FAQ

类型系统的行为

首先有几个需要格外说明的:

  • TypeScript使用结构化类型,即类型间的成员类型兼容即类型兼容。
  • TypeScript的类型时编译时的,在运行时并没有类型信息,无法从反射或元数据中拿到。

此外有些常见问题:

  • 没有setter的getter并没有体现出只读属性,这在TypeScript2.0+已修复
  • 更少参数的函数可以赋值给更多参数的函数;返回值更多的函数可以复制给返回值更少的函数
  • 任何类型都可以等价替代没有属性的interface
  • 类型别名只是别名而已,进行类型判断时使用的是别名对应的类型
  • 由于结构化类型,两个不同名但是结构相同的类型,实际上是相互兼容的,有个相关issue,但是尚没有结论
  • 由于TS的类型只存在于编译时,不能用运行时的typeofinstanceof判断类型。同样地,错误的TS类型转化也不会造成运行时的错误
  • 重载的最后一个声明签名对签名本身没有影响,所以为了获得重载本身的行为,需要添加额外的重载
1
2
3
4
5
function createLog(message: string): number;
function createLog(source: string, message: string): number;
function createLog(source: string, message?: string): number {
return 0;
}

一些常见的Feature Request

其他问题

空类的行为很奇怪

1
2
3
class Empty {}

var e: Empty = window;

和之前提到的一样,任何内容都可以赋值给空接口。所以一般来说,永远不要声明一个没有任何属性的类,对于子类而是如此。

如何比较类

TypeScript中,类进行结构上的比较,但是对于privateprotected属性除外。类在比较时,如果有成员是privateprotected,它们必须来自同一个声明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Alpha {
x: number;
}
class Bravo {
x: number;
}
class Charlie {
private x: number;
}
class Delta {
private x: number;
}

let a = new Alpha(),
b = new Bravo(),
c = new Charlie(),
d = new Delta();

a = b; // OK
c = d; // Error

classtypeof class的区别

1
2
3
4
5
6
class MyClass {
someMethod() {}
}
var x: MyClass;
// Cannot assign 'typeof MyClass' to MyClass? Huh?
x = MyClass;

上面混用了类型名和类本身,在JavaScript中,类仅仅是一个函数而已。而在TypeScript中,类名表示类实例的类型。

子类的属性在constructor中会被父类同名属性覆盖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Base {
// Default value
myColor = 'blue';

constructor() {
console.log(this.myColor);
}
}

class Derived extends Base {
myColor = 'red';
}

// Prints "blue", expected "red"
const x = new Derived();

直接原因是在子类constructor中,父类的constructor要先执行。见Stack Overflow的解释。

interfacedeclare class的区别

  • interface用来声明一种类型,不会生成实际代码。
  • declare class用来描述一个已有类的结构

为什么我导入的模块在编译后被删除了

TypeScript默认导入的模块不包含副作用,所以会移除不用于任何表达式的模块导入。使用import 'xxx';强制导入有副作用的模块。

tsconfig.json

  • 为什么exclude中的文件仍然会被编译器选中?
    • 当exclude的文件被其他include文件依赖时,仍然会被包含进来
  • 除了include外,还有没有指定包含文件的方式
    • files指定文件列表
    • 目录中添加///<reference path="">引入

参考《社会心理学》 David G. Myers 第8版

社会心理学 Part 2 - 社会影响 社会心理学 Part 3 - 社会关系 社会心理学 Part 4 - 应用

导论

是什么

什么是社会心理学?社会心理学研究:

  • 人们如何看待他人(社会思维)
  • 人们如何影响他人(社会影响)
  • 人们如何相互关联(社会关系)

类似经济学等其他人文社科,社会心理学亦有些基于上面几方面的重要观点

  • 我们个人构建起社会现实
  • 我们天生具有的社会直觉是强大的,但有些时候是危险的
  • 社会影响塑造行为,比如我们渴望彼此之间建立关联、渴望归属感、渴望得到他人的良好评价
  • 个人态度和性格倾向塑造行为
  • 社会行为同样也是生物性行为,我们是社会、心理、生物的共同产物

社会心理学不同于社会学:

  • 社会学研究团体,社会心理学研究个人
  • 社会学较难控制研究因素来做实验,社会心理学则可以控制变量来模拟实验

社会心理学不同于人格心理学:

  • 人格心理学研究个体间差异,社会心理学研究个体的社会因素
  • 人格心理学历史悠久,有诸多大师;社会心理学则比较年轻

另外不论是心理学还是别的学科,都是对一个事实的不同表述,每个学科都需要一些公设作为基础,它们之间没有高下之分。

价值观对研究的影响

直接影响来自于:课题选择、投身人群、实验目标等等方面。间接影响来自于:

  • 科学具有主观性,心理学则更是如此
  • 研究者在做价值判断时会受到个人价值观的影响
  • 当讨论从是什么变成怎么样时,就会把价值观纳入讨论之中

另外,特别澄清一个误解:社会心理学不过是常识而已嘛。社会心理学的结论往往简单易懂且贴近生活,所以会给人一种事后聪明型偏见。主要因为下面两点:

  • 心理学的高级结论往往正着说、反着说都有道理;就像谚语,比如“魔高一尺道高一丈”和“道高一尺魔高一丈”听起来都对
  • 事先得知结论然后去理解和通过努力发现结论完全不是一回事;当你有上述偏见时,尝试把两个相反结论放在一起做个选择题就知道了困难程度了

如何研究

好的假设:1)为研究指出方向;2)探测新的研究领域;3)具有应用价值。

好的理论:1)能对大范围观察结果做总结;2)对我们证明修改理论、进行新探索、指出可能应用方向给出清晰的预测

研究方法主要分为两种:相关研究实验研究

  • 相关研究发生在真实情景中,易于进行,只能发现相关性,因为无法控制变量,因而无法证明因果性
    • 对实验对象要足够有代表性(随机抽样
    • 认真组织问题,因为问题的顺序选项编制措辞都会影响被试的反应
  • 实验研究通过在实验室控制自变量,从而探寻因果性
    • 同样需要随机分配 + 施加不同外在条件保证额外因素的干扰消除
    • 需要自设实验情景,有时会面临伦理道德问题

社会思维

自我

这一章主要讨论人们如何评价、认识自己,如何维持自尊,社会思维如何产生等问题。

焦点效应使我们高估自己的突出程度,从而产生透明度错觉(实际注意我们的人比我们认为的少)。

自我概念

自我的部分是自我概念的基础,自我图式是我们组织自己所在世界的心理模板。自我参照效应让我们更好地以自我为中心组织记忆。同时,自我概念还包括我们想象中的自我

社会的部分同样扮演了很重要的角色。包括以下几点:

  • 我们在社会中扮演的角色
  • 社会同一性,即你身边的社会群体,诸如民族、信仰、性别等
  • 社会比较
  • 成功和失败经验。用积极的信息提高自尊会激发个体做出更大成就,全力以赴并取得了成功会使人感到更加自信有力。自尊不仅来自告诉孩子他们有多棒,还要让他们通过辛苦努力获得成功
  • 其他人的评价。与我们自我概念有关的是我们觉得他们如何评价我们,而不是他们实际上如何评价我们。自尊,是我们对他人如何评价我们的监控并作出相应反应的心理学尺度

自我受文化影响也很深。西方更盛行个人主义,赞美那些依靠自己的人。在亚洲、非洲、南美洲地区的文化则更重视集体主义,这些文化中的人们更多自我批评,而较少自我肯定。个人主义更容易观察到事物本质,集体主义更容易观察到事物间的联系。一个具有相互依赖自我的人会有更强烈的归属感。对于偏重个人主义的个体,积极情绪来自于效能感、出众和骄傲;来自集体主义的个体,积极情绪伴随着社会交往——亲密感、友好和尊重。

自我认识上,我们存在着一些天然的缺陷。

  • 我们会错误地解释我们的行为,行为相关的思想先于行动时,人们会感觉是按意愿做出这样行动的。
  • 我们会错误地预测他人和自己的行为,实验中发现人们预测别人的行为比预测自己的更为准确
  • 我们会错误地预测自己的感受
    • 一方面,人们很难预测自己未来情绪的强度和持续时间,比如预测自己谈恋爱、收到礼物、赢得比赛等后的感觉,又比如饥饿的人会错估自己的食量,且具有更高的购物冲动。这一点上可以粗略地总结为错误地想要得到某些东西。研究显示,好消息的情绪消失比自己预期的要快。
    • 另一方面,人们会高估消极情绪事件的持久性影响,以及过高地预期自己的痛苦。实际上,生理上和心理上人都具有强大的恢复能力

最后一点,人们在自我分析时,对一些原因并不显著的行为往往很难分析出原因。换句话说,我们对思维的结果比对思维的过程了解的更多。这带来两个结论:

  • 心理学研究中,自我报告通常靠不住
  • 日常生活中,人们分析或解释其经验的真实性无法保证报告准确性

自我效能 & 自尊

自我效能对自己能力、效率的乐观信念会获得更大回报。这种态度让你积极面对外界变化。心态上,自我效能高的人会认为身边发生的事“是受自己努力和技巧支配的”,而自我效能低的人会认为“是受外界力量支配的”。

人在对所做的事或他人对自己做的事失去控制时,会产生不愉快的压力情绪。长此以往会产生习得性无助,降低个体的抗挫能力。反之促进个体控制可以真正增强个体健康和幸福感。但是“过度的自由”也会降低幸福感,比如拥有一些无法反悔的事会让人心理更好受。

自尊是我们全面的自我评价。高自尊的人在自尊面临挑战时,会非常敌对。自尊带来的摩擦发生在熟人之间,比如兄弟姐妹、朋友。把自尊建立在良好的自我感觉,而不是分数、外貌、金钱和别人评价基础上的人,会一直感到状态良好。

自我服务偏见

我们加工和自我相关的信息时,会出现一种潜在偏见,即自我服务偏见

  • 我们把成功归于内部原因,失败归于外部因素
  • 我们总将成功与自我联系在一起,以保持良好的自我形象。因此,大多数人认为“今天的我比昨天更完善”
  • 我们认为自己比平均水平好,尤其是主观行为维度,如品德。教育也无法消除这种自我服务偏见。
  • 我们认为自己擅长的事情是更重要的,以此维持我们的自我形象
  • 绝大多数人盲目乐观,而较少居安思危
  • 虚假普遍性虚假独特性。即过高估计他人对自我观点的认同程度和把我们的成功、才智、品德看成超乎寻常。比如人们自己的生活变化时,可能会认为整个世界也在发生变化

总结来看,自我服务归因自我恭维的比较盲目乐观以及虚假普遍性是自我服务偏见的根源。这一机制有助于我们抵制抑郁,却会引起对他人、自己的错误评价,包括对自己所在群体的评价,从而带来群体冲突。

自我展示

人们对外展示的自我和真实的自我是不同的。最常出现的是虚伪的谦虚,比如对自我成就自传式的解释,这在匿名自我评价上就消失了,这种表浅的谦虚只是为了表现谦虚而不是内心的真实感受,这也能避免“获胜后的危险”。

人们为了减少失败结果的影响,可能会故意地自我妨碍,以维持对自己能力的信任,比如考试前不好好复习。

作为一种社会性动物,人们总是在向周围的观众表演。在熟悉的环境下,不需要意识参与就能完成。而在陌生环境里,比如和陌生异性聊天或参加一场宴会,我们就能确切地意识到营造形象的过程。在自我展示的程度上,有两种极端——行骗专家式的高自我监控和我行我素的第自我监控。更多人处在两个极端之间。尽管在集体主义国家,自我展示被抑制。但是自我美化依旧存在。

社会信念和判断

归因

人类天生想让整个世界合乎情理,至少能被自己解释。因而带来自然而然地对他人行为的归因

归因即探索行为的因果关系,比如“你不回我电话是因为你不关心我了!”。根据海德(Heider)始创的归因理论,归因分为两种:

  • 内因:性格归因,如“你不爱我了!”
  • 外因:情境性归因,如“我在打游戏”

伴随我们的归因天性,我们内置一些归因天赋:

  • 特质推断:不寻常的行为让我们更了解这个人
  • 常识性归因:共同反应(个体经常表现这种行为吗),区别性(不同情境下行为不同吗),一致性(别人也这样吗)共同作用得出结论

基本归因错误

遗憾的是,我们的归因天赋不完美的。李·罗斯发现个体在归因他人行为时,会低估情境因素的作用影响,而高估个人特质的影响。这种倾向即基本归因错误。当归因涉及个人利益时,这种错误更为明显。即使我们事先知道有情境因素存在,依旧会有这种错误。比如,我们会认为电影中反派的饰演者就是本人的真实反应,我们会认为考官或出题者会更聪明。类似地,如果某个人自己所持的观点被其他人反复表达,他也会认为其他人确实也持有这种观点。

应用在日常生活中,解释他人行为时,我们会犯基本归因错误,而对于自己的行为,我们却通常用情境因素解释。我们通常用描述行为、反应的词句解释自己,涉及到他人时,更经常用“他怎么怎么样”来描述。

基本归因错误可以来自于许多原因:

  • 行动者和观察者身处位置不同。作为观察者时,解释他人行为会更多聚焦在突出的行为人身上,从而易于忽略情境影响;相反,作为行动者本身时,自我和环境融为一体,观察联想时就会对情境因素更加敏感。
  • 聚焦观点偏见,即人们会更多关注被聚焦的个体,支持其观点。如观看聚焦犯人录像的观众几乎百分百认为犯人有罪,而观看聚焦审讯员录像的观众则会认为犯人是被迫认罪的。
  • 观点是随时变化的,回顾自己记忆时,会分配情境更多权重,近期行为更多归因情境,远期行为更多归因个人性质。比如Bob下周邀请我参加生日和2年后邀请归因是不同的
  • 自我觉知,注意力集中在自己身上时,更多归因自我特质而非特性。对于他人,越缺乏在不同情境下观察行为的机会,就越容易将行为归因他人人格,尤其是陌生人,对于我们很熟悉且在多种环境相处过的人,我们会对环境更加敏感。
  • 文化差异,集体主义更多归因情境,如“体制有问题”;个人主义更多归因个人特质

基本归因错误的原理很可能是,评价情境对他人的影响比归因个人特质要更花脑力,即将行为归因个体内在而非环境是种有效率的行为,且有些场景下归因个人特质确实是有效的。

研究归因错误是很有价值的。因为对于他人行为归因的不同会直接影响我们对他们的印象和感受,正如上面的“你不爱我了!”和“我在打游戏”。了解了这些,在日常生活中解释行为的时候,就更能以批判性的思维看待自己和他人的评价。

社会知觉和社会记忆

先入为主 —— 我们的预期会引导我们对信息的知觉和解释。比如,球迷们总认为裁判偏袒另一方。人总是主观的,事实上告诉我你从哪里看到了偏见,我甚至能获得有关你所持态度的线索。类似地,对于观点模糊的混合型信息,对立观点双方都会吸收信息并同化为支持自己,从而更坚定自己的观点。书中给出了一个例子——一个面无表情的男人面孔,在告诉你他是个慈善家和告诉你他是个纳粹分子时,你会对脸做出不同的认识。

事实就在那里,但我们的思维却积极地解释它,并根据不同的解释做出不同的行为。

解释的过程也会左右他人对我们的知觉,当我们说某人好话或坏话时,人们也会试图将那些特质和我们联系在一起

信念固着

一旦人们为错误的信息建立了理论基础,就很难再让他们否定这条错误的信息。建立理论基础的解释是人自然做出的,在解释形成后,会独立于最初推论出它的信息而存在。因此即使在信息被推翻后,被试依旧坚持自己归纳出的解释。这给我们一个启示:我们越是极力证明我们的理论和解释是正确的,我们就对挑战自己信念的信息越闭塞。

从进化学的角度看,这种归纳可以减轻理解负担;但是这个收益的代价是:我们成为自己思维方式的囚徒。实验发现,解释相反的观点可以减轻信念固着对我们思维的固有影响。

记忆重构

记忆与读书不同,它更像根据不连贯的笔记片段写一本书 —— John F. Kihlstrom, 1994

我们的记忆不像一个储物箱,需要什么可以直接取出。我们会无意识地修正和重构我们的回忆。

  • 一些细小的令人愉快的事件会让回忆比实际所经历的美好得多。一些不愉快或无聊的事情会被最小化,似乎消失在记忆里。这在旅行回忆中最为常见。(很像是大脑记忆机制的趋利避害)
  • 我们会改变同其他人关系的回忆,当记忆模糊时,当下的感觉会主导我们的回忆
  • 我们会重构过去行为的回忆,从而体现出“事后聪明式的偏差”。比如,当初要是怎么怎么样就好了。同时,回忆会变得更符合我们现在的观点
  • 一些暗示会影响回忆的结果(误导信息效应

整体来看,记忆的提取更像一个复杂的相互关联的过程。极易受上下文(如暗示、引导)影响,context不同,记忆启动的画面就不同。

直觉和判断

实验表明,无意识控制我们大多数行为,对绝大多数人来说,其日常生活不是由有意识的目标和经过深思熟虑的选择决定的,而是受内部心理过程的控制。比如在食物中看到虫子会感到恶心,跳舞时肢体动作的选择等等。我们意识小部分是受控制的,大部分是自动化的(冲动的、无需努力的、无意识的)。

  • 情绪反应是即时的,简单的喜欢、不喜欢、恐惧通常不涉及分析的过程。这些是伴随基因而来的。
  • 人在领域内拥有足够多专业知识时,可以依赖直觉获得问题答案,如下棋、写代码。高手通常会察觉到新手看不到的pattern。
  • 我们对事实、名字、过去经验的记忆是外显的,而对技能、条件特征的记忆是内隐的

看起来,我们的控制意识处理着最重要、最新鲜是事件,而把日常事情分配给其他系统处理。然而,直觉抑或说是无意识过程并没有想象中那么敏锐。比如事后聪明式判断。这些即错觉思维。

过度自信

过度自信现象会影响我们对目前知识的评价和对未来行为的预测。讽刺的是,能力不足会促进过度自信倾向,对能力的认识也是需要能力的。这种现象不是个别的:

  • 计划者通常会低估工程所需时间和精力
  • 投资专家都认为他们能够取得超过股市平均回报率的业绩
  • 过度自信优势会带来浩劫,如越战

验证性偏见:人们往往会寻找支持自己信念的信息,而非尝试证明自己的直觉不成立。因为这么做有助于个人证实自己意象。

有两种降低过度自信的方法:即时反馈,比如天气预报;设想自己判断可能出错的原因。同时考虑到过度自信的普遍性,我们也需要对别人独断性的陈述保持谨慎。

心理捷径

我们的认知系统在进化中形成了专门的心理捷径,让我们很容易就能形成印象,作出决定和生成解释。但这也会带来一些错误。

  • 代表性直觉,对某个事物进行评价时,在直觉的引导下,将其与某一类典型的心理表征进行比较。比如,告诉你一个人朝九晚五,很可能认为他是公务员;告诉你一个人996修福报,你很可能认为他是程序员。这会让我们忽略其他重要信息。
  • 易得性直觉,我们会优先从记忆中取出现成易得的信息做判断。比如,那些鲜明容易形象化的事件,如容易形象化症状的疾病,与那些较难形象化的疾病相比,被认为更易发生。再比如,那些小说、电影、电视中的虚构情节会给人留下印象,甚至也会影响我们随后的判断。如,我们认为飞机不如汽车安全。这会让我们过度重视生动鲜明的例证。
  • 反事实思维,容易想象的事件会影响我们对负罪、遗憾、挫败和宽慰的体验。比如我们队以一分之差输掉(赢得)比赛,相比差距20分,我们会感到更大的遗憾(宽慰)。铜牌会比银牌获得者更高兴。事件本身越重要,反事实思维强度就越大,如在酒驾中失去亲人会带来莫大的遗憾感。尽管如此,绝大部分人对已做事情的悔恨比没有做的事情的会很要小。对于已做事情,人们会更多可能感到歉意。

关联错觉

当我们期待发生某种重要联系时,会知觉到错觉相关,如吃巧克力会让我这一天更幸运。我们相信事件存在相关时,会更可能注意并回忆某些支持性的证据(幸存者效应)。

将随机事件知觉为有联系的倾向会让人产生控制错觉,即认为各种随机事件受自己影响。赌博中很常见,掷骰子希望点数较小时,出手会相对轻柔,希望点数较大时,出手相对较重。同时,由于趋均数回归,即随机现象会在期望附近上下波动的存在,在上一次或上几次偏离期望较远时,下一次更可能回归到期望附近。所以,当我们处在一个最低水平时,任何尝试行为看起来似乎都是有效的。这会让老师误认为自己的辅导生效了,或者看心理治疗书籍拯救了偶然的低迷情绪。

情绪影响判断

个体在乐观和意抑郁的情绪下,对世界的知觉不同。因为,我们的情绪会将与其相关的经验带入头脑并给我们的所见所闻着色。情绪对简单、自动化的思维影响比复杂、有意控制的思维要小。即我们思考越多,思维就越受情绪侵染。

自我实现预言

我们的社会信念和判断会影响我们的感觉和行动,由此改变自己的现实,这种观念引导我们以证实自己的方式行动即自我实现预言。老师认为学生是否有天赋,就会带来自我实现预言的现象。有趣的是,学生认为老师出色和有趣也会带来自我实现。推广来看,恋人之间也有这种现象。相反,一旦形成错误的社会信念,就可能引发他人做出某些行为反应以支持这些信念,即行为确证

确认他人期望的倾向具有局限性,且个体预先告知他人其期望时,可能会引起他人做出行动去克服期望。在采访中,对记者态度不明确的受访者会更经常硬核采访者期望。

整体来看,对于高效判断的偏向是直觉难以避免误判,我们直觉思维的缺陷是我们对复杂信息简化加工的心理捷径的副产品。这种直觉有时会让我们进入歧途,但大多数时候确实可以帮助我们做出高效迅速的决定。在有些职业里,需要考虑到这点。比如记者就需要了解如何避免上面这些认知偏见。

行为和态度

人的态度和行为具有什么关系呢?

ABC:感觉(affect)、行为倾向(behavior tendency)、认知(cognition)三种相互影响

态度决定行为?

我们精通并擅长为自己的行为寻找原因,但却非常不善于做我们已找到原因的事 —— Abelson 1972

社会心理学家最初认为态度和行为并没有明显关联。

  • 学生对于作弊的态度和他们实际作弊行为几乎没有关联
  • 道德伪善:表现出有道德水准,但实际上拒绝付出任何代价

因此,寄希望于改变态度来改变行为通常会以失败告终。

而后,社会心理学家发现行为和我们的态度不同是因为有其他因素的影响:

  • 外在表现受制于外部因素的影响,比如记名投票和匿名投票结果通常不同。为了避免外部因素的影响,可以使用伪途径法,类似“测谎仪”或看起来像“测谎仪”的东西
  • 长期观察个体行为、个体的通常行为,态度对于行为的预测会更明显
  • 测量的态度和行为直接相关时,态度确实可以预测行为

在行为是自发做出时,态度会潜在地起作用。当我们思考自己态度时,态度才会影响我们的行为。让人自我觉知或者本身自我意识感强的人,言行的一致性会更高。

总结来看,在限制条件的场景下,态度可以决定行为:

  • 其他因素最小化
  • 态度针对具体行为
  • 我们能清除意识到态度

行为如何影响态度

我们会逐步相信我们坚持的东西

下面一些现象都说明着,行为能影响态度,形成信念,被人所接受

  • 角色扮演,扮演一种新的社会角色时,起初我们可能会觉得虚假,但很快就能适应(融入角色、融入环境)。比如演员们入戏。
  • 语言变成信念,当一个人的话没有可以信服的外在解释时,语言就会变成信念。我们倾向于根据听众调整说话内容,并在说过之后相信歪曲的信息
  • 登门槛现象,即想让人帮大忙第一个有效策略是先请他们帮个小忙。当人们承诺公众行为并且认为这些行为是自觉作出时,他们会坚信自己的所作所为。商家的低价法策略就是利用了这一点。已经进行的行为会说服自己。
  • 邪恶的行为会侵蚀人的良心,通常导致攻击者贬抑受害者,以此为行为正当性辩护
  • 对他人的积极行为会增强对那个人的好感度,下次希望别人对自己有好感时,可以尝试引导别人对自己表露出积极行为
  • 我们会相信自己所坚持的所作所为,即使行为是受外在推动。政治仪式就是一个例子:唱国歌,参加演习。

行为为何会影响态度

有三个理论:自我展示:印象管理自我辩解:认知不协调自我知觉

自我展示认为人们行为改变的态度实际上是人们想给别人留下好印象,毕竟没有人愿意看起来自我矛盾。

自我辩解

认知不协调理论由费斯汀格(Leon Festinger)提出。它认为两种想法或认知在心理上不一致时,我们就会感到失调(即紧张)。因此我们要保持行为和认知间的一致性。理论主要用来解释态度和行为间的矛盾关系。

在此基础上,有个有趣的发现是,在行为的理由不足时,人们会更可能感到不舒服,因此要更相信自己的所作所为。这带来的应用有:

  • 温和地告诫不能为他们的所作所为(比如打扫房间)提供充分理由,孩子更可能将行为内化为合理且自发的。
  • 如果我们觉得要对自己行为负责时(而不是为了应付命令),我们的态度就会依从行为。鼓励和诱导好的行为可以激发孩子内化正确的态度

另外,当做出重要决策时,我们经常会过高评价自己的选择,而贬低放弃的选择,以此减少不协调程度。从而很轻易地带来决定——变成——信念。比如,当我们决定机票在1000元以下就坐飞机时,我们已经在想象坐飞机时的情景,机票涨到1000元以上时,我们很可能仍然会选择坐飞机。

自我知觉

自我知觉理论由贝姆(Daryl Bem)提出,它假设我们在态度还摇摆不定时,会处在局外人的态度观察自己,从而通过行动揭露自己的态度。比如,第二天要考试,前一天翻来覆去睡不着,我发现我失眠了,我意识到我很焦虑。

我们的表情和情态可以放大自我知觉,比如在害怕的时候哼一首愉快的小曲也许真有帮助。模仿别人表情和情态时,我们更能体验到别人的感受。实际上,我们在和他人交流时,通常通过保持行动、姿势等一致而让彼此感到和蔼可亲,从而造成情绪传染的效果。面部表情甚至也会影响我们的态度。比如在听取观点时,被要求保持点头姿势的人更容易同意此观点。

由于我们通过观察周围情境来解释自己的行为,自我知觉理论推导出一个结论:不必要的报酬会带来隐性的代价。给人们报酬让他们做自己喜欢的事会让他们将行为归因于报酬,从而削弱他们的自我知觉,即过度合理化效应。更准确地说:

  • 如果报酬和赞赏是针对人们的成就,则会增加个体的内部动机
  • 如果报酬是为了控制人们,而且人们也相信了是报酬导致了努力,那么会降低个体对工作的内在兴趣

因此,要适当地给予报酬:提供充分的理由,基于报酬和赞赏。抑或,反其道而行之,削减一些行为,比如寓言故事里说的,花钱让小孩制造噪音,再逐渐减少报酬,从而让小孩不再制造麻烦。

理论对比

认知不协调主要用来处理言行不一致的情况。言行不一致,不协调激活时,还会伴随排汗量增加、心率加快等生理表现,因为它会威胁到我们对自我价值的积极体验。自我知觉较弱的人,不协调感也会较弱。

自我知觉理论主要用在没有自我矛盾,态度并未完全形成时,它可以很好解释态度如何伴随行动形成和明确。

总结

要想养成习惯,那就去付诸行动。 —— 埃皮克提图

没有反应就没有接受,没有相关表达就不会产生印象

行为可以改变态度。我们用语言去解释某事时,会记得更牢。当你想形成某种态度时,不妨用行动去塑造。

0%