JavaScript 中的所有事物都是对象:字符串、数值、数组、函数...
此外,JavaScript 允许自定义对象。
JavaScript 提供多个内建对象,比如 String、Date、Array 等等。
对象只是带有属性和方法的特殊数据类型。
属性是与对象相关的值。
访问对象属性的语法是:
_objectName_._propertyName_
这个例子使用了 String 对象的 length 属性来获得字符串的长度:
var message="Hello World!";
var x=message.`length`;
在以上代码执行后,x 的值将是:
12
方法是能够在对象上执行的动作。
您可以通过以下语法来调用方法:
_objectName_._methodName_()
这个例子使用了 String 对象的 toUpperCase() 方法来将文本转换为大写:
var message="Hello world!";
var x=message.`toUpperCase()`;
在以上代码执行后,x 的值将是:
HELLO WORLD!
通过 JavaScript,您能够定义并创建自己的对象。
创建新对象有两种不同的方法:
这个例子创建了对象的一个新实例,并向其添加了四个属性:
person=new Object();
person.firstname="Bill";
person.lastname="Gates";
person.age=56;
person.eyecolor="blue";
替代语法(使用对象 literals):
person={firstname:"John",lastname:"Doe",age:50,eyecolor:"blue"};
本例使用函数来构造对象:
function person(firstname,lastname,age,eyecolor)
{
this.firstname=firstname;
this.lastname=lastname;
this.age=age;
this.eyecolor=eyecolor;
}
一旦您有了对象构造器,就可以创建新的对象实例,就像这样:
var myFather=new person("Bill","Gates",56,"blue");
var myMother=new person("Steve","Jobs",48,"green");
您可以通过为对象赋值,向已有对象添加新属性:
假设 personObj 已存在 - 您可以为其添加这些新属性:firstname、lastname、age 以及 eyecolor:
person.firstname="Bill";
person.lastname="Gates";
person.age=56;
person.eyecolor="blue";
x=person.firstname;
在以上代码执行后,x 的值将是:
Bill
方法只不过是附加在对象上的函数。
在构造器函数内部定义对象的方法:
function person(firstname,lastname,age,eyecolor)
{
this.firstname=firstname;
this.lastname=lastname;
this.age=age;
this.eyecolor=eyecolor;
this.changeName=changeName;
function changeName(name)
{
this.lastname=name;
}
}
changeName() 函数 name 的值赋给 person 的 lastname 属性。
现在您可以试一下:
myMother.changeName("Ballmer");
JavaScript 是面向对象的语言,但 JavaScript 不使用类。
在 JavaScript 中,不会创建类,也不会通过类来创建对象(就像在其他面向对象的语言中那样)。
JavaScript 基于 prototype,而不是基于类的。
JavaScript for...in 语句循环遍历对象的属性。
for (对象中的变量)
{
要执行的代码
}
注释:for...in 循环中的代码块将针对每个属性执行一次。
循环遍历对象的属性:
var person={fname:"Bill",lname:"Gates",age:56};
for (x in person)
{
txt=txt + person[x];
}
我建了一个前端学习群,有兴趣学习的同学可以关注我:前端学习交流 - 知乎
javascript对象
javascript中的所有事物都是对象:字符串,数值,数组,函数.....
此外,javascript允许自定义对象.
javascript提供多个內建对象,比如:String,Date,Array等等.对象只是带有属性和方法的特殊数据类型.
●布尔型可以是一个对象.
●数字型可以是一个对象.
●字符串可以是一个对象.
●日期是一个对象.
●正则表达式也是对象.
●数组是一个对象.
●甚至函数也可以是对象
var person={name:"John",age:31, favColor:"green",height:178};
属性:name,属性值:John. 以此类推.
javascript对象属性
可以通过两种方式访问对象属性.
objectName.propertyName
或者objectName['propertyName']
javascript的内置长度属性用于计算属性或字符串中的字符数.
var course={name:"JS",lesson:34};
document.write(course.name.length)
// ->2
对象方法
对象方法是一个包含函数定义的属性.
调用方法:
objectName.methodName()
使用String对象的toUpperCase()方法来将文本转换为大写:
var message="Coffee is lonely without cups";
var x=message.toUpperCase();
这样Coffee is lonely without cups 就会变成COFFEE IS LONELY WITHOUT CUPS
方法是作为对象属性存储的函数.
二 对象构造器
使用函数来构建对象:
function person(firstname,lastname,age,eyecolor){
this.firstname=firstname;
this.lastname=lastname;
this.age=age;
this.eyecolor=eyecolor; }
在javascript中,this通常指向我们正在执行的函数本身,或者指向该函数所属的对象(运行时)
创建javascript对象实例
一旦你有了对象构造器,就可以创建新的对象实例.
var myFather= new person("John","Doe",50,"blue");
var myMother=new person("Sally","Rally",48,"green") ;
document.write(myFather.age); //->50
document.write(myMother.name);//->Sally
myFather和myMother是person对象的实例.
三 对象初始化
使用对象literal或initializer语法创建单个对象.(literal:字面量.initializer:初始化程序,初始化器)
var John={name:"John",age:25};
var Loen={name:"Loen",age:28};
使用对象初始化器
空格和换行符并不重要.对象定义可以跨多行.
var John={
name:"John",
age:25};
var Loen={
name:"Loen",
age:28};
无论如何创建对象,访问属性和方法的语法不会改变.
document.write(Loen.age);
四 添加方法
方法是存储在对象属性中的函数.
创建对象函数:
function methodName(){code lines }
访问对象函数:
objectName.methodName()
this关键字是当前对象的引用,这意味着你可以使用this来访问对象属性和方法.
在构造函数中定义方法:
function person(name,age){
this.name=name;
this.age=age;
this.changeName=function (name){
this.name=name;}
}
var p=new person("Loen",28);
p.changeName("John");
在上面的例子中,我们定义了一个名为changeName的方法,该方法是一个函数,它接受参数名称并将其分配给对象的name属性.
添加方法:
可以在构造函数的外部定义一个函数,通过函数名关联到对象的属性上;
function person(name,age){
this.name=name;
this.age=age;
this.yearOfBirth=bornYear; //关联bornYear函数
}
function bornYear(){
return new Date().getFullYear()-this.age(); }
以上的代码中new Date().getFullYear()是获取今年完整的年份时间.如:2018.
我们已将对象的yearOfBirth属性赋予bornYear函数.当通过对象调用bornYear函数时,bornYear函数中的this指向这个对象.
将函数关联到对象属性时不需要写函数后边的括号.
调用方法:
function person(name,age){
this.name=name;
this.age=age;
this.yearOfBirth=bornYear;
}
function bornYear(){
return new Date().getFullYear()-this.age; }
var p=new person("Loen",27);
document.write(p.yearOfBirth());
// ->1991
通过对象的属性名调用函数而不是直接调用函数.
每天坚持进步一点点!
在seata整合nacos完成分布式的部署中,我们学习了Seata的集群部署,在这篇文章中,我们使用SpringBoot整合Seata实现分布式事务功能,此处使用的是Seata的AT模式。
我们存在2个服务 账户服务 account-service 和 订单服务 order-service,在订单服务中调用 账户服务。
订单服务中调用账户服务是通过 RestTemplate来实现的。
测试场景:
1、账户服务正常,订单服务正常,结果:账户服务正常扣款,产生订单。
2、账户服务正常,订单服务正常,在整个分布式事务中发生了异常,结果: 账户服务没有扣款,没有产生订单。
SpringBoot、Seata、Mybatis、nacos、druid
SpringBoot、Seata、Mybatis、nacos、Hikari
其中 SpringBoot 整合 Seata 是通过 seata-spring-boot-starter 这个来实现的,不使用 seata-all来实现。
账户服务,提供一个简单的扣除账户余额的功能,比较简单。
注意项:
1、开启自动数据源代理。
2、引入druid,不需要自动配置数据源。
3、注意事务分组
此处只引入几个 核心的 包,其余的包没有列在下方,比如mybatis等,注意和seata整合使用的是seata-spring-boot-starter
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.3.2</version>
</dependency>
create database seata_account;
use seata_account;
create table account(
id int unsigned auto_increment primary key comment '主键',
name varchar(20) comment '用户名',
balance bigint comment '账户余额,单位分'
) engine=InnoDB comment '账户表';
insert into account(id,name,balance) values (1,'张三',100000);
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB COMMENT ='AT transaction mode undo table';
每个业务库必须存在一张 undo_log表
提供一个接口,实现产生订单,扣除账户余额。
注意事项:
1、订单服务 关闭默认的数据源代理,自己配置数据源代理。
2、使用 Hikari数据源来实现,因为使用的是 AT模式,所以需要使用 DataSourceProxy 来代理数据源。
3、订单服务调用账户服务是采用的 RestTemplate,因此需要手动配置RestTemplate的拦截器,实现xid的传输。
4、在seata1.4.2中存在一个bug,如果业务表中数据类型是datetime类型,可能undolog无法序列化成功,可以采用timestamp或别的方式来处理。
5、业务库中需要存在 undo_log 表。
此处不引入 druid,注意和seata整合使用的是seata-spring-boot-starter
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.3.2</version>
</dependency>
package com.huan.seata.config;
import com.zaxxer.hikari.HikariDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class DataSourceConfig {
@Autowired
private DataSourceProperties dataSourceProperties;
@Bean
public DataSource dataSourceProxy() {
HikariDataSource hikariDataSource = new HikariDataSource();
hikariDataSource.setJdbcUrl(dataSourceProperties.getUrl());
hikariDataSource.setUsername(dataSourceProperties.getUsername());
hikariDataSource.setPassword(dataSourceProperties.getPassword());
hikariDataSource.setDriverClassName(dataSourceProperties.getDriverClassName());
return new DataSourceProxy(hikariDataSource);
}
}
在 AT模式种,数据源代理一定要是 DataSourceProxy这个。
@Service
@RequiredArgsConstructor
@Slf4j
public class BusinessServiceImpl implements BusinessService {
private final OrderService orderService;
private final RestTemplate restTemplate;
@Override
@GlobalTransactional(rollbackFor = Exception.class)
public void createAccountOrder(Integer accountId, Long amount, boolean hasException) {
System.out.println("createAccountOrder:" + RootContext.getXID());
// 1、远程扣减账户余额
remoteDebit(accountId, amount);
// 2、下订单
orderService.createOrder(accountId, amount);
if (hasException) {
throw new RuntimeException("发生了异常,分布式事物需要会滚");
}
}
private void remoteDebit(Integer accountId, Long amount) {
String url = "http://localhost:50001/account/debit?id=" + accountId + "&amount=" + amount;
String result = restTemplate.getForObject(url, String.class);
log.info("远程扣减库存结果:[{}]", result);
}
}
此处以订单服务来演示,如何和配置中心对应上的。
每个服务的事务分组可能不一样,但是需要和配置中心对应上。
比如:
order-service 中的配置分组为:seata.tx-service-group=tx_order_service_group
配置中心必须存在 service.vgroupMapping.tx_order_service_group=default 配置项,default是集群,是服务端配置文件中指定的
访问:http://localhost:50002/createOrder?accountId=1&amount=10&hasException=false
正常创建订单,和扣除余额。
访问: http://localhost:50002/createOrder?accountId=1&amount=10&hasException=true
不产生订单,不扣除余额。
A: 异常:io.seata.common.exception.FrameworkException: can not register RM,err:can not connect to services-server.
@Transactional 可与 DataSourceTransactionManager 和 JTATransactionManager 连用分别表示本地事务和XA分布式事务,大家常用的是与本地事务结合。当与本地事务结合时,@Transactional和@GlobalTransaction连用,@Transactional 只能位于标注在@GlobalTransaction的同一方法层次或者位于@GlobalTransaction 标注方法的内层。这里分布式事务的概念要大于本地事务,若将 @Transactional 标注在外层会导致分布式事务空提交,当@Transactional 对应的 connection 提交时会报全局事务正在提交或者全局事务的xid不存在。
由于业务提交,seata记录当前镜像后,数据库又进行了一次时间戳的更新,导致镜像校验不通过。
解决方案1: 关闭数据库的时间戳自动更新。数据的时间戳更新,如修改、创建时间由代码层面去维护,比如MybatisPlus就能做自动填充。
解决方案2: update语句别把没更新的字段也放入更新语句。
Seata 注册中心不能注册 0.0.0.0 或 127.0.0.1 的地址,当自动注册为上述地址时可以通过启动参数 -h 或容器环境变量SEATA_IP来指定。当和业务服务处于不同的网络时注册地址可以指定为 NAT_IP或公网IP,但需要保证注册中心的健康检查探活是通畅的。
以上的几个问题,来自seata官网 : http://seata.io/zh-cn/docs/overview/faq.html
代码地址:https://gitee.com/huan1993/spring-cloud-parent/tree/master/seata/seata-springboot-mybatis
*请认真填写需求信息,我们会在24小时内与您取得联系。