JS 高级 - ES6 中的类和继承、递归、ES6 新语法、闭包
1.instanceof 和 constructor
<script>
// instanceof:判断某个对象是否是某个构造函数创建的
function Student(name, age) {
this.username = name
this.userage = age
}
function Teacher(name, age) {
this.username = name
this.userage = age
}
let stu = new Student()
let tea = new Teacher()
console.log(stu instanceof Student, tea instanceof Student)
// console.log(typeof stu, typeof tea)
</script>
2. es6 class
JavaScript 语言中,生成实例对象的传统方法是通过构造函数,es6的class 的出现 基本上可以替代了es5的构造函数和原型,使之代码结构上更加简洁。
1.构造函数的写法跟传统的面向对象语言(比如 C++ 和 Java)差异很大,很容易让新学习js的程序员感到困惑。
2.ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。
3.新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
ES5 创建对象
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHi = function () {
console.log("hello, 我是" + this.name);
};
var p = new Person("lw", 36);
p.sayHi();
ES6 创建对象
<script>
// 同样实现两种继承:继承原型方法,继承构造函数
// 1.使用class定义类型,功能和构造函数类似
class Person {
// -添加构造器:为对象进行成员的初始化
constructor(name, age) {
this.username = name
this.userage = age
}
// 在原型中添加成员:直接写函数
say() {
console.log(this.username)
}
}
let per = new Person('jack', 20)
per.say()
</script>
回顾构造函数方式的继承
<script>
// 代码演示构造函数方式的原型继承及构造函数的继承
function Person(name, age) {
this.username = name
this.userage = age
}
Person.prototype.say = function() {
console.log(this.username)
}
function Student(name, age, gender) {
// this:调用当前Student构造函数所创建的对象
// this.username = name
// this.userage = age
// 调用父类Person的构造方法构建Student子类的成员
// 愿望:希望Person构造函数中的this指向student对象
Person.call(this, name, age)
this.usergender = gender
}
// 1.实现原型方法的继承
Student.prototype.say = Person.prototype.say
let stu = new Student('jack', 20, '男')
console.log(stu)
</script>
ES6-class实现默认继承
Class 可以通过
extends
关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
<script>
// 同样实现两种继承:继承原型方法,继承构造函数
// 1.使用class定义类型,功能和构造函数类似
class Person {
// -添加构造器:为对象进行成员的初始化
constructor(name, age) {
this.username = name
this.userage = age
}
// 在原型中添加成员:直接写函数
say() {
console.log(this.username)
}
}
// 定义一个子类Student,要实现继承Person类
// 通过 extends 实现继承
// 语法: class 子类 extends 父类
// 1.有了extends关键字,子类默认就会继承父类的所有原型中的成员
// 2.同时,默认也会继承父类的构造器
class Student extends Person {}
// 实例化对象的时候,会调用class的构造器(函数)
let stu = new Student('rose', 18)
stu.say()
</script>
ES6-class方式实现继承-添加子类构造器
<script>
// 同样实现两种继承:继承原型方法,继承构造函数
// 1.使用class定义类型,功能和构造函数类似
class Person {
// -添加构造器:为对象进行成员的初始化
constructor(name, age) {
this.username = name
this.userage = age
}
// 在原型中添加成员:直接写函数
say() {
console.log(this.username)
}
}
// 定义一个子类Student,要实现继承Person类
// 通过 extends 实现继承
// 语法: class 子类 extends 父类
// 1.有了extends关键字,子类默认就会继承父类的所有原型中的成员
// 2.同时,默认也会继承父类的构造器
class Student extends Person {
constructor(name, age, gender) {
// 使用this之前必须调用父类构造器
// 同时让父类构造器中的this为当前 子类对象
// 所以我们可以调用父类构造器来构建子类对象的成员
super(name, age)
// this.username = name
// this.userage = age
this.usergender = gender
}
}
// 实例化对象的时候,会调用class的构造器(函数)
let stu = new Student('rose', 18, '男')
console.log(stu)
stu.say()
</script>
案例:Tab栏切换
HTML结构
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<link rel="stylesheet" href="./css/tab.css" />
</head>
<body>
<div class="box" id="box">
<div class="hd">
<span class="current">体育</span>
<span>娱乐</span>
<span>新闻</span>
<span>综合</span>
</div>
<div class="bd">
<ul id="list">
<li class="current">我是体育模块</li>
<li>我的娱乐模块</li>
<li id="li3">我是新闻模块</li>
<li>我是综合模块</li>
</ul>
</div>
</div>
<script src="./js/tab.js"></script>
<script>
// 模拟一个tab栏插件
// 1.引入插件资源:js css
// 2.添加必须的结构和样式
// 3.初始化,实现功能
new MyTab('#box')
</script>
</body>
</html>
CSS代码
* {
margin: 0;
padding: 0;
}
ul {
list-style: none;
}
.box {
width: 400px;
height: 300px;
border: 1px solid #ccc;
margin: 100px auto;
}
.hd {
width: 100%;
height: 45px;
display: flex;
}
.hd span {
display: inline-block;
/*将行内元素转换成行内块元素,宽高才起作用*/
flex: 1;
background-color: pink;
line-height: 45px;
text-align: center;
cursor: pointer;
border-right: 1px solid #fff;
}
.hd span:last-child {
border-right: none;
}
.hd span.current {
/*交集选择器,标签指定式选择器*/
background-color: rgb(240, 175, 35);
/*紫色*/
}
.bd li {
height: 255px;
background-color: rgb(189, 189, 189);
display: none;
/*设置隐藏*/
}
.bd li.current {
display: block;
/*显示*/
}
JS代码
class MyTab {
// 添加一个构造器,接收用户传入的参数
// 构造器的功能是为对象添加成员
constructor(selector) {
// 获取Tab栏整个结构窗口
let box = document.querySelector(selector)
this.spans = box.querySelectorAll('.hd > span')
this.lis = box.querySelectorAll('.bd li')
this.box = box
// this:是指当前所创建的对象
// this调用方法,那么方法中的this就是当前的对象
this.tabClick()
}
// 实现功能
tabClick() {
console.log(this)
// 将this存储到一个变量中,以便后期在函数中使用的时候还是我们所需要的this
// 命名一般有两个常见命名方式: _this that
let _this = this
// // 循环遍历所有spans,绑定事件
this.spans.forEach(function(ele, index) {
ele.addEventListener('click', function() {
console.log(_this)
// 在事件处理函数中,实现再次排他
// 1.获取当前有acitve的span元素,清除它的active样式
_this.box.querySelector('.hd span.current').classList.remove('current')
// 2.为当前被单击的span添加active样式
ele.classList.add('current')
_this.box.querySelector('.bd li.current').classList.remove('current')
// 2.为当前被单击的span添加active样式
_this.lis[index].classList.add('current')
})
})
}
}
递归函数
递归函数:函数内部直接或者间接的调用自己
递归的要求:
- 自己调用自己(直接或者间接)
- 要有结束条件(出口)
递归函数主要是化归思想
,将一个复杂的问题简单化,主要用于解决数学中的一些问题居多。
- 把要解决的问题,归结为已经解决的问题上。
- 一定要考虑什么时候结束让函数结束,也就是停止递归(一定要有已知条件)
什么是递归
在程序中,所谓的递归,就是函数自己直接或间接的调用自己。调用自己分两种:
- 直接调用自己
- 间接调用自己
就递归而言最重要的就是跳出结构,因为跳出了才可以有结果.
化归思想
化归思想: 将一个问题由难化易,由繁化简,由复杂化简单的过程称为化归,它是转化和归结的简称。
递归思想就是将一个问题转换为一个已解决的问题来实现
假如有一个函数f
, 如果它是递归函数的话, 那么也就是说函数体内的问题还是转换为 f
的形式.
function f() {
... f( ... ) ...
}
常规应用
1, 2, 3, 4, 5, ..., 100 求和
- 首先假定递归函数已经写好, 假设是
foo
. 即foo(100)
就是求1
到100
的和 - 寻找递推关系. 就是
n
与n-1
, 或n-2
之间的关系:foo( n ) == n + foo( n - 1 )
var res = foo(100);
var res = foo(99) + 100;
var res = foo(98) + 99 + 100
........................
var res = foo(2) + 3 + 4 + 5 .....+ 98 + 99 + 100
var res = foo(1) + 2 + 3 + 4 + 5 .....+ 98 + 99 + 100
var res = 1 + 2 + 3 + 4 + 5 .....+ 98 + 99 + 100
将递推结构转换为递归体
function foo(n){
return n + foo( n - 1 );
}
上面就是利用了化归思想:
- 将 求 100 转换为 求 99
- 将 求 99 转换为 求 98
- ......
- ......
- 将求 2 转换为 求 1
- 求 1 结果就是 1
- 即: foo( 1 ) 是 1
练习:
-
计算1-100之间所有数的和
// 1, 2, 3, 4, 5, ..., 100 求和 // 递归思想就是将一个问题转换为一个已解决的问题来实现 // 1. 100 + (1到99的和) // 2. 100 + (99 + (1到98的和)) // 3. 100 + (99 + (98 + (1到97的和))) // ..... // 99. 100 + (99 + ( 98 + ( 97 + (96 +(95.....(2 + 1)))))) function getSum(n){ // 5 if(n == 1){ return 1 } return n + getSum(n-1) } console.log(getSum(100))
-
斐波那契数列
<script> /*1 1 2 3 5 8 13 21 ...*/ // 1.前面两位固定:结束递归的条件,如果第一位或第二们,直接返回1 // 2.从第三位开始,每一位是前面两位数字的和 // 独白: // 1.如果要求第100位的数字,那么先得到第99位和第98位数字 // 2.如果要求第99位的数字,那么先得到第98位和第97位数字 // 3.如果要求第98位的数字,那么先得到第97位和第96位数字 // ...... // 97:如果要求第4位的数字,那么先得到第3位和第2位数字 // 98:如果要求第3位的数字,那么先得到第2位和第1位数字 // 99.如果要求第2位的数字,直接返回1 // 100.如果要求第1位的数字,直接返回1 function fibonaci(n){ if(n == 2 || n == 1){ return 1 } return fibonaci(n-1) + fibonaci(n-2) } console.log(fibonaci(5)) </script>
案例-生成目录结构
传统方式生成目录
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<script>
var data = {
parent: '递归案例',
children: [
{
parent: '目录1',
children: []
},
{
parent: '目录2',
children: []
},
{
parent: '目录3',
children: [
{
parent: '子目录1',
children: [
{
parent: '第一层',
children: [
{
parent: '第二层',
children: [
{
parent: '第三层',
children: [
{
parent: '第四层',
children: [
{
parent: '第五层',
children: [
{
parent: '第六层',
children: []
}
]
}
]
}
]
}
]
}
]
}
]
},
{
parent: '子目录2',
children: []
}
]
},
{
parent: '目录4',
children: [
{
parent: '第一层',
children: []
}
]
}
]
}
let str = '递归案例<br>'
// 读取第一层children
data.children.forEach(function(one) {
str += ' ' + one.parent + '<br>'
// 判断是否有下一层,下面的条件说明有下一层
if (one.children && one.children.length > 0) {
one.children.forEach(function(two) {
str += ' ' + two.parent + '<br>'
// 判断是否还有下一层,如果有则继续遍历拼接
if (two.children && two.children.length > 0) {
two.children.forEach(function(three) {
str +=
' ' + three.parent + '<br>'
// 判断是否还有下一层,如果有则继续遍历拼接
if (three.children && three.children.length > 0) {
three.children.forEach(function(forth) {
str +=
' ' +
forth.parent +
'<br>'
// 判断是否还有下一层,如果有则继续遍历拼接
if (forth.children && forth.children.length > 0) {
forth.children.forEach(function(fifth) {
str +=
' ' +
fifth.parent +
'<br>'
// 。。。。
})
}
})
}
})
}
})
}
})
document.write(str)
</script>
</body>
</html>
递归方式生成目录
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<script>
var data = {
parent: '递归案例',
children: [
{
parent: '目录1',
children: []
},
{
parent: '目录2',
children: []
},
{
parent: '目录3',
children: [
{
parent: '子目录1',
children: [
{
parent: '第一层',
children: [
{
parent: '第二层',
children: [
{
parent: '第三层',
children: [
{
parent: '第四层',
children: [
{
parent: '第五层',
children: [
{
parent: '第六层',
children: [
{
parent: '第100层',
children: []
}
]
}
]
}
]
}
]
}
]
}
]
}
]
},
{
parent: '子目录2',
children: []
}
]
},
{
parent: '目录4',
children: [
{
parent: '第一层',
children: []
}
]
}
]
}
let str = '递归案例<br>'
let count = 0
function printDir(data, count) {
count++
// 判断是否还有下一层,如果有则继续遍历拼接
if (data.children && data.children.length > 0) {
data.children.forEach(function(dir) {
// repeat:将前面的字符串重复指定的次数
str += ' '.repeat(count) + dir.parent + '<br>'
// 调用函数实现递归生成目录
printDir(dir, count)
})
}
}
printDir(data, count)
document.write(str)
</script>
</body>
</html>
函数参数默认值
<script>
// 函数的形参是一个变量,可以直接赋值一个默认值
// 1.如果调用函数时没有传递参数,则使用默认值
// 2.如果调用函数时传递了参数,则使用传递的参数
function show(msg = '大家一起快活呀') {
// msg = msg || '大家一起快活呀'
// if (!msg) {
// msg = '大家一起快活呀'
// }
console.log(msg)
}
show() // 打印 大家一起快活呀
show('搞笑不') // 打印 搞笑不
</script>
对象简写
在定义对象的时候,如果属性名和变量名一致,那么可以实现简写
const name = "悟空";
const skill = "72变";
const say = function () { }
const obj = {
name, skill, say
}
console.log(obj);// {name:"悟空",skill:"72变",say:function(){}}
对象的方法也可以简写
const obj = {
say() {
console.log(this);
}
}
解构
提供更加方便获取数组中元素或者对象中属性的写法
获取数组中的元素
const [a, b, c, d] = [1, 2, 3, 4];
console.log(a, b, c, d);// 1,2,3,4
元素交互顺序
let a = 1111;
let b = 2222;
[b, a] = [a, b];
console.log(a, b);// 2222 1111
获取对象中的属性(重点)
const obj = {
name: "悟空",
skill: "72变",
say() { }
}
const { name, skill,say } = obj;
console.log(name, skill,say);// 悟空 72变 function(){}
解构-补充
<script>
let name = 'rose'
// 解构:就是从对象或者数组中提取元素
// 数组解构
// let arr = [12, 3, 245, 364]
// let [a, b, ...c] = arr
// Rest剩余的 element元素 must be必须 last最后 element元素
// 不能解构出中间的元素
// let [a, ...b, c] = arr
// console.log(a, b, c)
// let a = 10,
// b = 20
// ;[b, a] = [a, b]
// console.log(a, b)
// 对象解构
let obj = {
name: 'jack',
age: 20,
gender: '男',
computer: {
brand: '搞不清',
price: 2000
}
}
// Identifier标识 'name' has already been已经被 declared声明
// 对象解构的时候,只能解构出对象中已有属性,如果解构的名称在对象中不存在,则返回undefined
// 我们在解构的时候可以为解构出的成员设置别名,后续使用应该使用别名
// let { name: username, age } = obj
// console.log(username, age)
// 实现二次解构
let { computer:{brand} } = obj
console.log(brand)
</script>
拓展(展开)运算符 || 剩余运算符
通过 ...
符号来获取剩下的参数
函数内获取
function show(a, ...all) { // 只能放最后
console.log(a);
console.log(all);
}
show(1);// 1 []
show(1, 2, 3);// 1 [2,3]
数组内获取
const [a, ...rest] = [1, 2, 3, 4, 5];
console.log(a); // 1
console.log(rest);// [2, 3, 4, 5]
对象内获取
const obj={
name:"悟空",
skill:"72变",
say(){}
}
const {name,...others}=obj;
console.log(name); // 悟空
console.log(others); // {skill: "72变", say: ƒ}
Set
永远不会有重复元素的对象
可以理解为不重复的数组
const set = new Set([1, 5, 3, 4]);
set.add(5);
set.add(5);
console.log(set);
Set对象转为数组
const set = new Set([1, 5, 3, 4]);
set.add(5);
set.add(5);
console.log(set);
const arr = [...set];// 将set对象转数组
console.log(arr);
闭包
闭包的概念
在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了*变量的函数
闭:封闭:函数内部声明的成员默认在函数外部不能使用
包:包裹:函数内部包裹函数,内部所声明的函数就可以访问内部的成员
在JavaScript中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则产生闭包。简单的来说闭包就是,一个具有封闭的对外不公开的, 包裹结构, 或空间.
在JavaScript中函数可以构成闭包. 一般函数是一个代码结构的封闭结构, 即包裹的特性, 同时根据作用域规则, 只允许函数访问外部的数据, 外部无法访问函数内部的数据, 即封闭的对外不公开的特性. 因此说函数可以构成闭包
产生闭包的条件
当内部函数访问了外部函数的变量的时候,就会形成闭包。
// 1.函数内部声明函数--嵌套函数--内部函数
// 2.内部声明的函数使用了外部声明的成员
// 3.返回了内部函数,外面进行接收,通过这个函数使用内部成员
闭包要解决什么问题?
- 闭包内的数据不允许外界访问
- 要解决的问题就是间接访问该数据
函数就可以构成闭包, 要解决的问题就是访问到函数内部的数据
我们观察下面的函数foo
,在foo
内部有一个变量num
,能否在函数外部访问到这个变量num
呢?
function foo () {
var num = 123;
return num;
}
var res = foo();
console.log( res ); // => 123
分析:
在上面的代码中,确实可以访问到num
这个函数内部的变量。但是能不能多次访问呢?
不能,因为每次访问都得重新调用一次foo
函数,每次调用都会重新创建一个num = 123
,然后返回。
解决思路
函数内的数据不能直接在函数外被访问,是因为作用域的关系,上级作用域不能直接访问下级作用域中的数据。
但是如果反过来,下级作用域可以直接访问上级作用域中的数据。那么如果在函数foo
内定义一个函数,那么在这个内部函数中是可以直接访问foo
中的num
的。
function foo() {
var num = Math.random();
function func() {
return num;
}
return func;
}
var f = foo();
// f可以直接访问num,而且多次访问,访问的也是同一个,并不会返回新的num
var res1 = f();
var res2 = f();
闭包的作用
保护私有变量不被修改
如何获得超过一个数据
函数的返回值只能有一个,那按照上面的方法,我们只能对函数内部的一个数据进行操作。怎么操作函数内的多个数据呢?
可以使用对象,代码如下:
function foo () {
var num1 = Math.random();
var num2 = Math.random();
//可以将多个函数包含在一个对象内进行返回,这样就能在函数外部操作当前函数内的多个变量
return {
num1: function () {
return num1;
},
num2:
推荐阅读
-
SSM三大框架基础面试题-一、Spring篇
什么是Spring框架?
Spring是一种轻量级框架,提高开发人员的开发效率以及系统的可维护性。
我们一般说的Spring框架就是Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是核心容器、数据访问/集成、Web、AOP(面向切面编程)、工具、消息和测试模块。比如Core Container中的Core组件是Spring所有组件的核心,Beans组件和Context组件是实现IOC和DI的基础,AOP组件用来实现面向切面编程。
Spring的6个特征:
核心技术:依赖注入(DI),AOP,事件(Events),资源,i18n,验证,数据绑定,类型转换,SpEL。
测试:模拟对象,TestContext框架,Spring MVC测试,WebTestClient。
数据访问:事务,DAO支持,JDBC,ORM,编组XML。
Web支持:Spring MVC和Spring WebFlux Web框架。
集成:远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。
语言:Kotlin,Groovy,动态语言。
列举一些重要的Spring模块?
Spring Core:核心,可以说Spring其他所有的功能都依赖于该类库。主要提供IOC和DI功能。
Spring Aspects:该模块为与AspectJ的集成提供支持。
Spring AOP:提供面向切面的编程实现。
Spring JDBC:Java数据库连接。
Spring JMS:Java消息服务。
Spring ORM:用于支持Hibernate等ORM工具。
Spring Web:为创建Web应用程序提供支持。
Spring Test:提供了对JUnit和TestNG测试的支持。
谈谈自己对于Spring IOC和AOP的理解
IOC(Inversion Of Controll,控制反转)是一种设计思想:
在程序中手动创建对象的控制权,交由给Spring框架来管理。IOC在其他语言中也有应用,并非Spring特有。IOC容器实际上就是一个Map(key, value),Map中存放的是各种对象。
将对象之间的相互依赖关系交给IOC容器来管理,并由IOC容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。IOC容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。在实际项目中一个Service类可能由几百甚至上千个类作为它的底层,假如我们需要实例化这个Service,可能要每次都搞清楚这个Service所有底层类的构造函数,这可能会把人逼疯。如果利用IOC的话,你只需要配置好,然后在需要的地方引用就行了,大大增加了项目的可维护性且降低了开发难度。
Spring中的bean的作用域有哪些?
1.singleton:该bean实例为单例
2.prototype:每次请求都会创建一个新的bean实例(多例)。
3.request:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
4.session:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有效。
5.global-session:全局session作用域,仅仅在基于Portlet的Web应用中才有意义,Spring5中已经没有了。Portlet是能够生成语义代码(例如HTML)片段的小型Java Web插件。它们基于Portlet容器,可以像Servlet一样处理HTTP请求。但是与Servlet不同,每个Portlet都有不同的会话。
Spring中的单例bean的线程安全问题了解吗?
概念用于理解:大部分时候我们并没有在系统中使用多线程,所以很少有人会关注这个问题。单例bean存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。
有两种常见的解决方案(用于回答的点):
1.在bean对象中尽量避免定义可变的成员变量(不太现实)。
2.在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal(线程本地化对象)中(推荐的一种方式)。
ThreadLocal解决多线程变量共享问题(参考博客):https://segmentfault.com/a/1190000009236777
Spring中Bean的生命周期:
1.Bean容器找到配置文件中Spring Bean的定义。
2.Bean容器利用Java Reflection API创建一个Bean的实例。
3.如果涉及到一些属性值,利用set方法设置一些属性值。
4.如果Bean实现了BeanNameAware接口,调用setBeanName方法,传入Bean的名字。
5.如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader方法,传入ClassLoader对象的实例。
6.如果Bean实现了BeanFactoryAware接口,调用setBeanClassFacotory方法,传入ClassLoader对象的实例。
7.与上面的类似,如果实现了其他*Aware接口,就调用相应的方法。
8.如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执postProcessBeforeInitialization方法。
9.如果Bean实现了InitializingBean接口,执行afeterPropertiesSet方法。
10.如果Bean在配置文件中的定义包含init-method属性,执行指定的方法。
11.如果有和加载这个Bean的Spring容器相关的BeanPostProcess对象,执行postProcessAfterInitialization方法。
12.当要销毁Bean的时候,如果Bean实现了DisposableBean接口,执行destroy方法。
13.当要销毁Bean的时候,如果Bean在配置文件中的定义包含destroy-method属性,执行指定的方法。
Spring框架中用到了哪些设计模式?
1.工厂设计模式:Spring使用工厂模式通过BeanFactory和ApplicationContext创建bean对象。
2.代理设计模式:Spring AOP功能的实现。
3.单例设计模式:Spring中的bean默认都是单例的。
4.模板方法模式:Spring中的jdbcTemplate、hibernateTemplate等以Template结尾的对数据库操作的类,它们就使用到了模板模式。
5.包装器设计模式:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
6.观察者模式:Spring事件驱动模型就是观察者模式很经典的一个应用。
7.适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式、Spring MVC中也是用到了适配器模式适配Controller。
还有很多。。。。。。。
@Component和@Bean的区别是什么
1.作用对象不同。@Component注解作用于类,而@Bean注解作用于方法。
2.@Component注解通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(我们可以使用@ComponentScan注解定义要扫描的路径)。@Bean注解通常是在标有该注解的方法中定义产生这个bean,告诉Spring这是某个类的实例,当我需要用它的时候还给我。
3.@Bean注解比@Component注解的自定义性更强,而且很多地方只能通过@Bean注解来注册bean。比如当引用第三方库的类需要装配到Spring容器的时候,就只能通过@Bean注解来实现。
@Configuration
public class AppConfig {
@Bean
public TransferService transferService {
return new TransferServiceImpl;
}
}
<beans>
<bean id="transferService" class="com.kk.TransferServiceImpl"/>
</beans>
@Bean
public OneService getService(status) {
case (status) {
when 1:
return new serviceImpl1;
when 2:
return new serviceImpl2;
when 3:
return new serviceImpl3;
}
}
将一个类声明为Spring的bean的注解有哪些?
声明bean的注解:
@Component 组件,没有明确的角色
@Service 在业务逻辑层使用(service层)
@Repository 在数据访问层使用(dao层)
@Controller 在展现层使用,控制器的声明
注入bean的注解:
@Autowired:由Spring提供
@Inject:由JSR-330提供
@Resource:由JSR-250提供
*扩:JSR 是 java 规范标准
Spring事务管理的方式有几种?
1.编程式事务:在代码中硬编码(不推荐使用)。
2.声明式事务:在配置文件中配置(推荐使用),分为基于XML的声明式事务和基于注解的声明式事务。
Spring事务中的隔离级别有哪几种?
在TransactionDefinition接口中定义了五个表示隔离级别的常量:ISOLATION_DEFAULT:使用后端数据库默认的隔离级别,Mysql默认采用的REPEATABLE_READ隔离级别;Oracle默认采用的READ_COMMITTED隔离级别。ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
Spring事务中有哪几种事务传播行为?
在TransactionDefinition接口中定义了八个表示事务传播行为的常量。
支持当前事务的情况:PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)。
不支持当前事务的情况:PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
其他情况:PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED。
二、SpringMVC篇
什么是Spring MVC ?简单介绍下你对springMVC的理解?
Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把Model,View,Controller分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。
Spring MVC的工作原理了解嘛?
image.png
Springmvc的优点:
(1)可以支持各种视图技术,而不仅仅局限于JSP;
(2)与Spring框架集成(如IoC容器、AOP等);
(3)清晰的角色分配:前端控制器(dispatcherServlet) , 请求到处理器映射(handlerMapping), 处理器适配器(HandlerAdapter), 视图解析器(ViewResolver)。
(4) 支持各种请求资源的映射策略。
Spring MVC的主要组件?
(1)前端控制器 DispatcherServlet(不需要程序员开发)
作用:接收请求、响应结果,相当于转发器,有了DispatcherServlet 就减少了其它组件之间的耦合度。
(2)处理器映射器HandlerMapping(不需要程序员开发)
作用:根据请求的URL来查找Handler
(3)处理器适配器HandlerAdapter
注意:在编写Handler的时候要按照HandlerAdapter要求的规则去编写,这样适配器HandlerAdapter才可以正确的去执行Handler。
(4)处理器Handler(需要程序员开发)
(5)视图解析器 ViewResolver(不需要程序员开发)
作用:进行视图的解析,根据视图逻辑名解析成真正的视图(view)
(6)视图View(需要程序员开发jsp)
View是一个接口, 它的实现类支持不同的视图类型(jsp,freemarker,pdf等等)
springMVC和struts2的区别有哪些?
(1)springmvc的入口是一个servlet即前端控制器(DispatchServlet),而struts2入口是一个filter过虑器(StrutsPrepareAndExecuteFilter)。
(2)springmvc是基于方法开发(一个url对应一个方法),请求参数传递到方法的形参,可以设计为单例或多例(建议单例),struts2是基于类开发,传递参数是通过类的属性,只能设计为多例。
(3)Struts采用值栈存储请求和响应的数据,通过OGNL存取数据,springmvc通过参数解析器是将request请求内容解析,并给方法形参赋值,将数据和视图封装成ModelAndView对象,最后又将ModelAndView中的模型数据通过reques域传输到页面。Jsp视图解析器默认使用jstl。
SpringMVC怎么样设定重定向和转发的?
(1)转发:在返回值前面加"forward:",譬如"forward:user.do?name=method4"
(2)重定向:在返回值前面加"redirect:",譬如"redirect:http://www.baidu.com"
SpringMvc怎么和AJAX相互调用的?
通过Jackson框架就可以把Java里面的对象直接转化成Js可以识别的Json对象。具体步骤如下 :
(1)加入Jackson.jar
(2)在配置文件中配置json的映射
(3)在接受Ajax方法里面可以直接返回Object,List等,但方法前面要加上@ResponseBody注解。
如何解决POST请求中文乱码问题,GET的又如何处理呢?
(1)解决post请求乱码问题:
在web.xml中配置一个CharacterEncodingFilter过滤器,设置成utf-8;
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
(2)get请求中文参数出现乱码解决方法有两个:
①修改tomcat配置文件添加编码与工程编码一致,如下:
<ConnectorURIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
②另外一种方法对参数进行重新编码:
String userName = new String(request.getParamter("userName").getBytes("ISO8859-1"),"utf-8")
ISO8859-1是tomcat默认编码,需要将tomcat编码后的内容按utf-8编码。
Spring MVC的异常处理 ?
统一异常处理:
Spring MVC处理异常有3种方式:
(1)使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver;
(2)实现Spring的异常处理接口HandlerExceptionResolver 自定义自己的异常处理器;
(3)使用@ExceptionHandler注解实现异常处理;
统一异常处理的博客:https://blog.csdn.net/ctwy291314/article/details/81983103
SpringMVC的控制器是不是单例模式,如果是,有什么问题,怎么解决?
是单例模式,所以在多线程访问的时候有线程安全问题,不要用同步,会影响性能的,解决方案是在控制器里面不能写成员变量。(此题目类似于上面Spring 中 第5题 有两种解决方案)
SpringMVC常用的注解有哪些?
@RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。
@RequestBody:注解实现接收http请求的json数据,将json转换为java对象。
@ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户。
SpingMvc中的控制器的注解一般用那个,有没有别的注解可以替代?
一般用@Controller注解,也可以使用@RestController,@RestController注解相当于@ResponseBody + @Controller,表示是表现层,除此之外,一般不用别的注解代替。
如果在拦截请求中,我想拦截get方式提交的方法,怎么配置?
可以在@RequestMapping注解里面加上method=RequestMethod.GET。
怎样在方法里面得到Request,或者Session?
直接在方法的形参中声明request,SpringMVC就自动把request对象传入。
如果想在拦截的方法里面得到从前台传入的参数,怎么得到?
直接在形参里面声明这个参数就可以,但必须名字和传过来的参数一样。
如果前台有很多个参数传入,并且这些参数都是一个对象的,那么怎么样快速得到这个对象?
直接在方法中声明这个对象,SpringMVC就自动会把属性赋值到这个对象里面。
SpringMVC中函数的返回值是什么?
返回值可以有很多类型,有String, ModelAndView。ModelAndView类把视图和数据都合并的一起的。
SpringMVC用什么对象从后台向前台传递数据的?
通过ModelMap对象,可以在这个对象里面调用put方法,把对象加到里面,前台就可以拿到数据。
怎么样把ModelMap里面的数据放入Session里面?
可以在类上面加上@SessionAttributes注解,里面包含的字符串就是要放入session里面的key。
SpringMvc里面拦截器是怎么写的:
有两种写法,一种是实现HandlerInterceptor接口,另外一种是继承适配器类,接着在接口方法当中,实现处理逻辑;然后在SpringMvc的配置文件中配置拦截器即可:
<!-- 配置SpringMvc的拦截器 -->
<mvc:interceptors>
<!-- 配置一个拦截器的Bean就可以了 默认是对所有请求都拦截 -->
<bean id="myInterceptor" class="com.zwp.action.MyHandlerInterceptor"></bean>
<!-- 只针对部分请求拦截 -->
<mvc:interceptor>
<mvc:mapping path="/modelMap.do" />
<bean class="com.zwp.action.MyHandlerInterceptorAdapter" />
</mvc:interceptor>
</mvc:interceptors>
注解原理:
注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象。通过代理对象调用自定义注解的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池
三、Mybatis篇
什么是MyBatis?
MyBatis是一个可以自定义SQL、存储过程和高级映射的持久层框架。
讲下MyBatis的缓存
MyBatis的缓存分为一级缓存和二级缓存,一级缓存放在session里面,默认就有,
二级缓存放在它的命名空间里,默认是不打开的,使用二级缓存属性类需要实现Serializable序列化接口,
可在它的映射文件中配置<cache/>
Mybatis是如何进行分页的?分页插件的原理是什么?
1)Mybatis使用RowBounds对象进行分页,也可以直接编写sql实现分页,也可以使用Mybatis的分页插件。
2)分页插件的原理:实现Mybatis提供的接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql。
举例:select * from student,拦截sql后重写为:select t.* from (select * from student)t limit 0,10
简述Mybatis的插件运行原理,以及如何编写一个插件?
1)Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、
Executor这4种接口的插件,Mybatis通过动态代理,
为需要拦截的接口生成代理对象以实现接口方法拦截功能,
每当执行这4种接口对象的方法时,就会进入拦截方法,
具体就是InvocationHandler的invoke方法,当然,
只会拦截那些你指定需要拦截的方法。
2)实现Mybatis的Interceptor接口并复写intercept方法,
然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,
记住,别忘了在配置文件中配置你编写的插件。
Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不?
1)Mybatis动态sql可以让我们在Xml映射文件内,
以标签的形式编写动态sql,完成逻辑判断和动态拼接sql的功能。
2)Mybatis提供了9种动态sql标签:trim|where|set|foreach|if|choose|when|otherwise|bind。
3)其执行原理为,使用OGNL从sql参数对象中计算表达式的值,
根据表达式的值动态拼接sql,以此来完成动态sql的功能。
#{}和${}的区别是什么?
1)#{}是预编译处理,${}是字符串替换。
2)Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值(有效的防止SQL注入);
3)Mybatis在处理${}时,就是把${}替换成变量的值。
为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?
Hibernate属于全自动ORM映射工具,
使用Hibernate查询关联对象或者关联集合对象时,
可以根据对象关系模型直接获取,所以它是全自动的。
而Mybatis在查询关联对象或关联集合对象时,
需要手动编写sql来完成,所以,称之为半自动ORM映射工具。
Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
1)Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,
association指的就是一对一,collection指的就是一对多查询。
在Mybatis配置文件中,
可以配置是否启用延迟加载lazyLoadingEnabled=true|false。
2)它的原理是,使用CGLIB创建目标对象的代理对象,
当调用目标方法时,进入拦截器方法,
比如调用a.getB.getName,
拦截器invoke方法发现a.getB是null值,
那么就会单独发送事先保存好的查询关联B对象的sql,
把B查询上来,然后调用a.setB(b),
于是a的对象b属性就有值了,
接着完成a.getB.getName方法的调用。
这就是延迟加载的基本原理。
MyBatis与Hibernate有哪些不同?
1)Mybatis和hibernate不同,它不完全是一个ORM框架,
因为MyBatis需要程序员自己编写Sql语句,
不过mybatis可以通过XML或注解方式灵活配置要运行的sql语句,
并将java对象和sql语句映射生成最终执行的sql,
最后将sql执行的结果再映射生成java对象。
2)Mybatis学习门槛低,简单易学,程序员直接编写原生态sql,
可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,
例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,
一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性,
如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。
3)Hibernate对象/关系映射能力强,数据库无关性好,
对于关系模型要求高的软件(例如需求固定的定制化软件)
如果用hibernate开发可以节省很多代码,提高效率。
但是Hibernate的缺点是学习门槛高,要精通门槛更高,
而且怎么设计O/R映射,在性能和对象模型之间如何权衡,
以及怎样用好Hibernate需要具有很强的经验和能力才行。
总之,按照用户的需求在有限的资源环境下只要能做出维护性、
扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。
MyBatis的好处是什么?
1)MyBatis把sql语句从Java源程序中独立出来,放在单独的XML文件中编写,
给程序的维护带来了很大便利。
2)MyBatis封装了底层JDBC API的调用细节,并能自动将结果集转换成Java Bean对象,
大大简化了Java数据库编程的重复工作。
3)因为MyBatis需要程序员自己去编写sql语句,
程序员可以结合数据库自身的特点灵活控制sql语句,
因此能够实现比Hibernate等全自动orm框架更高的查询效率,能够完成复杂查询。
简述Mybatis的Xml映射文件和Mybatis内部数据结构之间的映射关系?
Mybatis将所有Xml配置信息都封装到All-In-One重量级对象Configuration内部。
在Xml映射文件中,<parameterMap>标签会被解析为ParameterMap对象,
其每个子元素会被解析为ParameterMapping对象。
<resultMap>标签会被解析为ResultMap对象,
其每个子元素会被解析为ResultMapping对象。
每一个<select>、<insert>、<update>、<delete>
标签均会被解析为MappedStatement对象,
标签内的sql会被解析为BoundSql对象。
什么是MyBatis的接口绑定,有什么好处?
接口映射就是在MyBatis中任意定义接口,然后把接口里面的方法和SQL语句绑定,
我们直接调用接口方法就可以,这样比起原来了SqlSession提供的方法我们可以有更加灵活的选择和设置.
接口绑定有几种实现方式,分别是怎么实现的?
接口绑定有两种实现方式,一种是通过注解绑定,就是在接口的方法上面加
上@Select@Update等注解里面包含Sql语句来绑定,
另外一种就是通过xml里面写SQL来绑定,在这种情况下,
要指定xml映射文件里面的namespace必须为接口的全路径名.
什么情况下用注解绑定,什么情况下用xml绑定?
当Sql语句比较简单时候,用注解绑定;当SQL语句比较复杂时候,用xml绑定,一般用xml绑定的比较多
MyBatis实现一对一有几种方式?具体怎么操作的?
有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次,
通过在resultMap里面配置association节点配置一对一的类就可以完成;
嵌套查询是先查一个表,根据这个表里面的结果的外键id,
去再另外一个表里面查询数据,也是通过association配置,
但另外一个表的查询通过select属性配置。
Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别?
能,Mybatis不仅可以执行一对一、一对多的关联查询,
还可以执行多对一,多对多的关联查询,多对一查询,
其实就是一对一查询,只需要把selectOne修改为selectList即可;
多对多查询,其实就是一对多查询,只需要把selectOne修改为selectList即可。
关联对象查询,有两种实现方式,一种是单独发送一个sql去查询关联对象,
赋给主对象,然后返回主对象。另一种是使用嵌套查询,嵌套查询的含义为使用join查询,
一部分列是A对象的属性值,另外一部分列是关联对象B的属性值,
好处是只发一个sql查询,就可以把主对象和其关联对象查出来。
MyBatis里面的动态Sql是怎么设定的?用什么语法?
MyBatis里面的动态Sql一般是通过if节点来实现,通过OGNL语法来实现,
但是如果要写的完整,必须配合where,trim节点,where节点是判断包含节点有
内容就插入where,否则不插入,trim节点是用来判断如果动态语句是以and 或or
开始,那么会自动把这个and或者or取掉。
Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
第一种是使用<resultMap>标签,逐一定义列名和对象属性名之间的映射关系。
第二种是使用sql列的别名功能,将列别名书写为对象属性名,
比如T_NAME AS NAME,对象属性名一般是name,小写,
但是列名不区分大小写,Mybatis会忽略列名大小写,
-
JS 高级 - ES6 中的类和继承、递归、ES6 新语法、闭包