一、Javascript介绍
1.1 JavaScript的组成部分
- ECMAScript:JavaScript的基础部分,包含:变量、数据类型、控制语句、函数、数组、运算符、对象
- BOM:浏览器对象模型
- DOM:文档对象模型(重点)
二、JavaScript的基础
2.1 JavaScript的数据类型
- number数据类型
- boolean数据类型
- string数据类型
- Object对象数据类型
- Array数组数据类型(实际本身属于Object对象数据类型)
- function函数数据类型
- null空数据类型
- NaN数据类型,只有报错的时候才会出现,NaN===》not a number
- undefined数据类型,只有报错的时候才会出现,not defined
2.2 JavaScript的变量
方式1:在定义的时候,就给定初始值( 常用)
var 变量名 = 值;
比如:
var num = 10;
方式2:先定义,后面再赋值
var 变量名;
变量名 = 值;
比如:
var num;
num = 10;
方式3:连关键字var都省略了,直接变量名赋值即可(不可取)
变量名 = 值;
比如:
num = 10;
方式4:局部变量
let 变量名 = 值;
比如:
let i = 0;
注:使用typeof方法,获取变量的数据类型
<html>
<head>
<meta charset="utf-8">
<title>数据类型</title>
</head>
<body>
<script type="text/javascript">
//定义number变量
var num1 = 10;
document.write(typeof num1);
document.write("<br />");
var num2 = 9.9;
document.write(typeof num2);
document.write("<br />");
var num3 = "9.9";
document.write(typeof num3);
document.write("<br />");
var num4 = false;
document.write(typeof num4);
document.write("<br />");
var num5 = new Object();
document.write(typeof num5);
document.write("<br />");
var num6 = new Array();
document.write(typeof num6);
document.write("<br />");
var num7 = function add(){}
document.write(typeof num7);
document.write("<br />");
var num8 = null;
document.write(typeof num8);
document.write("<br />");
var a = "abc";
var b = "123";
document.write(a - b);
document.write("<br />");
document.write(num9 - 10);
</script>
</body>
</html>
2.3 JavaScript的输入和输出
- 输入
var msg = window.prompt("请输入姓名:");
document.write(msg);
- 输出
方式1:以弹出框的形式输出,弹出框的输出优先级最高
window.alert("这里是输出的内容");
方式2:在网页的body部分进行输出,将双引号中的内容直接输出到页面上,如果是标签,那么浏览器会解析标签
document.write("这里是输出内容");
方式3:在控制台进行输出
console.log("控制台输出");
2.4 运算符
- 赋值运算符
=,+=,-=,*=,/=,%=
- 算数运算符
加:+,两个作用,做加法运算或者拼接减:-乘:*除:/模:%
- 比较运算符
>,>=,<,<=,==,!=,===(要求:数据类型和值都要一致才为true)
- 逻辑运算符
与运算符:&&或者&或运算符:||或者|非运算符:!
- 三元运算符:?
var 变量 = 判断语句?值1:值2;
2.5 分支语句
- if分支
if(判断语句){
}else if(判断语句){
}else{
}
- switch分支语句
var 变量 = 值;
switch(变量){
case 值1:
语句;
break;
case 值2:
语句;
break;
default
语句;
}
2.6 循环语句
- for循环
for(var 变量;判断语句;改变变量的值){
循环的代码块;
}
- while循环
var 变量 = 值;
while(判断){
循环的代码块;
改变变量的值;
}
- do…while循环
var 变量 = 值;
do{
循环的代码块;
改变变量的值;
}while(判断);
2.7 JavaScript数组
定义数组的方法两种
方式1:先定义数组,再向里面添加元素
var arr = new Array();
arr.push("a");
arr.push("b");
arr.push(12);
arr.push(true);
document.write(arr);
方式2:定义数组就将元素添加到数组中
//定义数组的第二种方式
var arr2 = [1,2,3,4,5,6];
document.write(arr2);
数组的操作:
- 数组的遍历
方式1:如果只是查看,可以直接输出数组名即可
document.write(数组名);
方式2:普通遍历
for(定义变量;判断语句;改变变量的值){
document.write(数组名[i]);
}
方式3:for… in循环
for(var 变量 in 数组名称){
//强调:这里的变量仍然指的是下标
document.write(数组名称[变量])
}
- 冒泡排序
//定义一个数组
var arr = [9,7,10,15,6,4,12];
document.write("排序前:"+arr);
document.write("<br />");
//使用冒泡排序
//外层循环控制遍历多少次
for(var i = 0;i < arr.length-1;i++){
//内层循环控制每一轮遍历中的相邻比较
for(var j = 0; j < arr.length - 1 - i;j++){
//将相邻的两个做比较
if(arr[j] > arr[j+1]){
var temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
document.write("排序后:"+arr);
- 选择排序
//定义一个数组
var arr = [9,7,10,15,6,4,12];
document.write("排序前:"+arr);document.write("<br />");
//使用选择排序
for(var i = 0;i < arr.length-1;i++){
for(var j = i+1;j < arr.length;j++){
//比较
if(arr[i] > arr[j]){
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
document.write("排序后:"+arr);
- 折半查找
//定义一个数组,要求必须是有序列
var arr = [1,2,3,4,5,6,7,8,9,12,13,14,16,18,20,25];
//定义变量,记录要找的元素值
var key = window.prompt("请输入一个要找的数:");
//定义变量,记录最小下标
var min = 0;
//定义变量,记录最大下标
var max = arr.length-1;
//定义一个变量,记录最终的下表值
var index = -1;
//使用折半查找
while(min < max){
//计算中间下标
var mid = parseInt((min + max) / 2);
//用中间下标处的元素值和要找的值进行比较
if(arr[mid] > key){
max = mid -1;
}else if(arr[mid] < key){
min = mid + 1;
}else{
index = mid;
break;
}
}
//判断index的值
if(index != -1){
document.write("当前的下表:"+mid);
}else{
document.write("该元素不在数组中");
}
作业讲解
- 第一题:
/*
1、在弹出框输入一串字符串,统计出数字个数、大写字母个数和小写字母个数,提示:js中将
单个字符转换成ASCII码表值得方法是charCodeAt(index);
*/
//弹出框输入一个字符串,并且用一个变量来接受
var str = prompt("请输入一个字符串:");
//定义变量,记录数字的个数
var countNumber = 0;
//定义变量,记录大写字母个数
var countBig = 0;
//定义变量,记录小写字母的个数
var countSmall = 0;
//对字符串进行循环遍历,取到每一个字符
for(var i = 0;i < str.length;i++){
//将每一个字符转换成ASCII码表值
var code = str.charCodeAt(i);
//判断code的值范围
if(code >= 48 && code <= 57){
countNumber++;
}else if(code >= 65 && code <= 90){
countBig++;
}else if(code >= 97 && code <= 122){
countSmall++;
}
}
//输出个数:
document.write("数字个数:"+countNumber+",大写字母个数:"+countBig+",小写字母个数:"+countSmall);
- 第二题:
/*
2、用JavaScript在页面输出99乘法表
*/
for(var i = 1;i <= 9;i++){
for(var j = 1;j <= i;j++){
document.write(j+"×"+i+"="+(i*j)+" ");
}
document.write("<br />");
}
- 第三题:
//定义二维数组
var arr = new Array();
for(var i = 0;i < 7;i++){
arr[i] = new Array(i);
}
//给二维数组赋值
for(var i = 0;i < 7;i++){
for(var j = 0;j <i+1;j++){
if(j == 0 || i == j){
arr[i][j] = 1;
}else{
arr[i][j] = arr[i-1][j]+arr[i-1][j-1];
}
}
}
//遍历二维数组
for(var i = 0;i < arr.length;i++){
for(var j = 0;j <= i;j++){
document.write(arr[i][j]+" ");
}
document.write("<br />");
}
- 第四题
/*
水仙花数
*/
for(var i = 100;i <= 999;i++){
//将i依次剥离,剥离出各位,十位和百位
var a = i % 10;
var b = parseInt(i / 10) % 10;
var c = parseInt(i / 100);
var res = a*a*a + b*b*b + c*c*c;
//判断
if(res == i){
document.write(i+" ");
}
}
- 第五题
/*
5、在弹出框中输入一组数字,判断该数是否是回文数,提示:对称得数称为回文数,比如:2332
*/
var str = prompt("请输入一个数:");
//定义变量,记录是否是回文数,假设是回文数
var flag = true;
//对str进行循环
for(var i = 0;i < str.length / 2;i++){
//对称比较
if(str.charAt(i) != str.charAt(str.length-1-i)){
flag = false;
break;
}
}
if(flag){
document.write(str+"是回文数");
}else{
document.write(str+"不是回文数");
}
二、函数
函数概念:将实现特定功能的代码块进行封装,该方法最好是实现单一功能,提高代码的复用性
js中,函数分为 内置函数(系统函数) 和 自定义函数(重点)
2.1内置函数
1 alert(内容) 弹出消息框
2 typeof(值) 查看类型
3 parseInt(值); 转成整型
4 parseFloat(值): 转成小数
5 isNaN(值) 判断是否是非数字,如果是非数字,返回true,反之亦然
6 prompt(值1[,值2]) 弹出输入框,值1是标题,值2是初始值,点击确定,返回输入的值,否则返回null
7 confirm(值) 弹出确认框 点击确认,返回true,反之亦然
2.2自定义函数
语法:最常见的函数定义方式
//定义函数
function 函数名称([参数列表]){
函数封装的代码片段;
[return 变量]
}
//调用函数
函数名([参数列表]);
- 无参的函数事例
//定义函数,实现两个数的和
function add(){
var a = 10;
var b = 20;
var c = a + b;
document.write("c = "+c);
}
//调用函数
add();
- 有参的函数事例,无返回值
//定义函数,实现任意两个数的和
function add2(a,b){
var c = a + b;
document.write("c = "+c);
}
add2(10,10);
- 有参数的函数事例,有返回值
//定义函数,实现任意两个数的和,并且将和返回到函数外面
function add3(a,b){
var c = a + b;
return c;
}
//调用函数
var res = add3(20,20);
document.write("res = "+res);
- 可变参数的函数
//定义一个函数,实现任意个数的数据相加和,并返回
function add4(){
//定义变量,记录和
var sum = 0;
//在js中有可以接受实际参数的对象,arguments
//遍历arguments对象,获取到每一个实际参数的值
for(var i = 0;i < arguments.length;i++){
sum = sum + arguments[i];
}
return sum;
}
//调用函数
var res1 = add4(10,20,100,200);
document.write("res1 = "+res1);
- 字面量的函数,要求:必须先定义,后使用
//定义一个字面量函数
var add5 = function(){
var i = 10;
var j = 20;
var k = i + j;
return k;
}
三、面向对象(了解)
- 字面量创建对象
//用字面量定义类
var person = {
name:"张三",
age:18, sex:"男",
doIt:function(){
document.write("我在学习JavaScript");
}
}
//创建对象
document.write(person.name+"<br />");
document.write(person.age+"<br />");
document.write(person.sex+"<br />");
person.doIt();
- 使用父类创建对象
//使用父类创建对象
var user = new Object();
user.name = "张三";
user.age = 19;
user.doId = function(){
document.write("我已经不想些Java代码了");
}
document.write(user.name);
user.doId();
- 使用工厂思想,用父类对象完成面向对象
//使用工厂的思想,完成面向对象
function createBook(bookName,price){
var obj = new Object();
obj.bookName = bookName;
obj.price = price;
return obj;
}
//获取对象
var b1 = createBook("Java编程思想",108);
document.write(b1.bookName);
document.write(b1.price);
- 使用构造方法的方式创建对象
//使用构造方法形式定义
var Product = function(productName,price){
this.productName = productName;
this.price = price; this.doIt = function(){
document.write("想念java的第N天");
}
}
//创建对象
var prod = new Product("旺仔",5.5);
document.write(prod.productName);
document.write(prod.price);
prod.doIt();
四、DOM对象
- document object model 文档对象模型—-》操作document对象
我们可以把每个标签看成是一个对象,标签的属性也就是对象的属性,DOM就是通过document来操作每一个标签
DOM对象的应用入门:
//在body上面输出内容
document.write("哈哈哈哈");
document.write("<br />");
//用document对象操作标签内容
document.write("<span id = 'one'>你是一条猪儿虫</span>");
document.write("<br />");
document.write("<span id ='two'>我是一条小青龙</span>");
4.1 DOM对象对标签的操作
- 查询(获取标签):在body中正常的进行布局,然后由js获取标签
//第一种获取标签的方式:根据id获取,只能获取一个
var e1 = document.getElementById("id的名称");
//方式2:根据标签中的class属性名获取
var e2 = document.getElementsByClassName("class的名称");
//方式3:根据标签名称获取
var e3 = document.getElementsByTagName("标签的名称");
//方式4:直接使用querySelector("选择器");实现获取标签,只能获取唯一的一个
var e4 = document.querySelector("#选择器");
//方式5:如果要获取的结果集由多个,querySelectorAll("选择器");
var e5 = document.querySelectorAll(".选择器");
- 修改,通过document对象获取了对象之后,可以去获取的标签用js完成css样式的修改或者属性值的修改
//先获取,然后修改css的样式(修改css:修改原有属性,新增css属性等操作)
//操作1:修改标签的css
function updateCss1(){
//获取标签
var e = document.querySelector("#img1");
//修改该标签的css属性
e.style.border = "2px red solid";
e.style.width = "200px";
e.style.height = "200px";
}
//操作2:修改属性
function updateAttr(){
var e = document.querySelectorAll(".img2");
for(var i = 0;i < e.length;i++){
e[i].src = "../img/1.jpg";
}
}
//给指定的标签增加css的选择器
function addCss(){
var e = document.querySelectorAll("li");
//完整的
for(var i = 0;i < e.length;i++){
if(e[i].hasAttribute("class")){
e[i].setAttribute("class",e[i].getAttribute("class")+" col");
}else{
e[i].setAttribute("class","col");
}
}
}
- 新增:使用js在页面动态的创建标签
//操作1:创建标签
function createDiv(){
var oDiv = document.createElement("div");
//可以给新创建的div设置css属性
oDiv.style.width = "100px";
oDiv.style.height = "100px";
oDiv.style.border = "2px red solid";
oDiv.style.float = "left";
//将创建的div挂在到#out的div里面
var parent = document.querySelector("#out");
parent.appendChild(oDiv);
}
//操作2:使用innerHTML = 值;方法,将字符串类型的标签语句,嵌入到一个父标签下
function createHtml(){
var e = document.querySelector("#temp");
var oSpan= "<span style = 'color:red'>哈啊哈哈哈</span>";
e.innerHTML = oSpan;
}
//操作3:获取双标签之间的文本数据
function getText(){
var e = document.querySelector("#temp");
var val = e.innerText; console.log(val);
}
//操作4:修改或者在双标签之间添加文本数据
function updateText(){
var e = document.querySelector("#temp");
e.innerText = e.innerText+"哈哈哈哈";
}
- 删除
//操作1:删除一个标签
function delete1(){
//获取标签
var oDiv = document.getElementById("out");
oDiv.remove();
}
//操作2:一定要考虑是否能够删除完全
function delete2(){
var oImg = document.getElementsByClassName("img1");
var len = oImg.length;
for(var i = 0;i < len;i++){
oImg[0].remove();
}
}
//操作3:
function delete3(){
var oImg = document.querySelectorAll(".img1");
for(var i = 0;i < oImg.length;i++){
oImg[i].remove();
}
}
五、事件操作
- 在页面需要鼠标、键盘进一步操作之后,才会触发某些js函数的执行。
5.1 鼠标事件(掌握)
1、鼠标单击事件:在标签上添加onclick = "函数名()";
2、鼠标双击事件:在标签上添加ondblclick = "函数名()";
3、鼠标移入事件:onmouseenter = "函数名称()";或者onmouseover = "函数名称()"
4、鼠标移出事件:onmouseleave = "函数名称()";或者onmouseout = "函数名称()";
5、鼠标移动事件:onmousemove = "函数名称()";
5.2 键盘事件
1、键盘按下事件:window.onkeydown2、键盘松开事件:window.onkeyup
5.3 表单事件(掌握)
1、获取焦点事件:onfocus2、失去焦点事件:onblur3、内容改变事件:onchange
5.4 js中注册事件的方法
方式1: 在标签中通过注册事件来完成
<button id="btn1" onclick="f1()">按钮1</button>
<script>
function f1() {
alert("ok1");
}
</script>
方式2:在js中完成注册
语法:
标签对象.onxx事件=function(){}
<button id="btn2">按钮2</button>
<script>
var btn2 = document.getElementById("btn2");
btn2.onclick = function() {
alert("ok2");
}
</script>
<button id="" name="btn">按钮2</button>
<button id="" name="btn">按钮2</button>
<button id="" name="btn">按钮2</button>
<button id="" name="btn">按钮2</button>
<button id="" name="btn">按钮2</button>
<script>
var btns = document.getElementsByName("btn");
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = function() {
alert("ok");
}
}
</script>
方式3:绑定事件
在js中写代码
绑定语法:
标签对象.addEventListener("事件类型",function(){代码})
标签对象.addEventListener("事件类型",函数名)
移出绑定语法
标签对象.removeEventListener("事件类型",函数名)
事件类型不能有 “on”
var b1 = document.getElementById("b1");
b1.addEventListener("click", f1)
b1.addEventListener("click", f2)
function f1() {
alert("b1");
}
function f2() {
alert("b2");
}
var b2 = document.getElementById("b2");
b2.addEventListener("click", function() {
b1.removeEventListener("click", f1);
})
六、BOM
brwoser object model 浏览器对象模型,将浏览器的各个组成部分封装对象
我们操作的是window对象
window是js的顶级对象(类似于java的Object),而document也只是window对象的属性之一
组成:
1 window:窗口对象
2 screen:屏幕对象
3 history: 历史对象
4 location:地址栏对象
6.1、window:窗口对象
该对象不需要创建,直接用即可
window.属性
window.方法()
window.是可以省的
6.2、window属性—screen
//除开标题高度的窗体高度
console.log(screen.availWidth + ":" + screen.availHeight);
//分辨率
console.log(screen.width + ":" + screen.height);
6.3、window属性—history
语法
history.back() 返回上一个页面
history.forward() 前进下一个页面
history.go() 可以返回/前进n个页面
比如
history.go(1) 前进一个页面
history.go(-1) 返回一个页面
6.4、window属性—location(重点)
js跳转
语法
location.href="url地址"
6.5、window属性—document
获取载入当前页面的页面地址 (需要服务器的支持)
语法:
document.referrer
比如a页面跳转到b页面,在b页面中打印documendt.referrer 获取的是a页面地址
6.6、window对象的方法
alert()
confirm()
prompt();
open();
close();
拓展
什么是冒泡和捕获
js的事件流:元素节点按照特定的顺序传播
捕获阶段—-》目标阶段—->冒泡阶段
现在的所有的浏览器都把捕获特点给删除了(以前网景浏览器默认采用的捕获·)
冒泡:子元素到父元素的过程
捕获:父元素到子元素的过程
我们可以通过.addEventListener的语法来控制捕获或冒泡:
addEventListener("click",function(){}, true/false);
true:捕获
false/不写: 冒泡
js如何解决冒泡
使用关键词event
用法和this相同,需要传递到函数中
onclick="event"
function xx(e){
}
作用:
1 通过event可以获得点击那个元素对象
e.target
2 能够阻止冒泡
event.stopPropagation()
例子:阻止a标签跳转
<a href="http://www.baidu.com" onclick="ff2(event)">跳转</a>
<script>
function ff(o, e) {
alert(o.id);
//阻止冒泡
event.stopPropagation()
// console.log(e.target == o);
}
3 阻止默认 (阻止元素本身的行为)
e.preventDefault()
event和this的区别
this:绑定的那个元素
event:操作的那个元素
事件委托
利用事件冒泡的特性,将本应该注册在子元素上的处理事件注册在父元素上,这样点击子元素时发现其本身没有相应事件就到父元素上寻找作出相应
1.减少DOM操作,提高性能。2.随时可以添加子元素,添加的子元素会自动有相应的处理事件。
子页面嵌套iframe
<iframe src="../0604/demo1.html" frameborder="0" width="100%" height="200px" scrolling="no"></iframe>
innerHTML和innerText的区别
以上都都能够操作标签体内容,
innerHTML能够识别标签
innerText 不能识别标签
div可编辑
<div id="d" contenteditable="true">
</div>
js控制样式(css)
语法:
标签对象.style.样式属性=”值”
注意:如果样式属性是右-组成的,则我们需要去掉-,-后面的单词首字母大写,
比如:font-size 需要写成 fontSize
5、让超链接失去跳转功能
<a href="javaScript:;"></a>
<a href="javaScript:void(0)"></a>
string
字符串,是js的基本数据类型之一,同时也是内置对象
内置对象,不用new,直接用
语法法:
1 .length
2 .indexOf(内容)--->返回第一个下标
3 .lastIndexOf(内容)---->返回最后一个下标
4 .substr(开始位置,截取长度)
5 .substring(开始位置,结束位置)
6 charAt()
7 charCodeAt()
8 split()分割 通过分隔符转成数组
ASCII
//a-z 97~122
//A-Z 65~90
//0-9 48~57
正则表达式
正则表达式就是来做表单验证的
语法
var reg=//; -- 表达式
reg.test(字符串)--->如果合法则返回true,否则false
^ : 以....开头
$ : 以...结尾
[]: 匹配[]中的任意一个字符
[1,2,3,a,b,c]
[a-zA-Z0-9]
[^] :匹配不在[]中的任意一个字符
\s : 匹配空白字符
\S : 匹配非空白字符
\w: 匹配单词 (单词=字母、数字、_) ==> [a-zA-Z0-9_]
\W: 匹配非单词
\d :匹配数字 ==>[0-9]
\D :匹配非数字
. :匹配任意字符
\. : 匹配.
| :或者 需要搭配()
{n,m} 匹配左边规则的个数必须>=n && <=m
{n,} 匹配左边规则的个数必须>=n
{n} 匹配左边规则的个数必须=n
? :匹配0次或1次 -->{0,1}
+ :匹配1一次或多次 -->{1,}
* :匹配0次多次 {0,}
js中的定时器
语法 :
1 setInterval(参数1,参数2); -->返回当前计时器的序列名
每隔xx毫秒执行一次该函数
参数1:函数
参数2:毫秒
方式1:使用匿名函数
setInterval(function(){},毫秒)
方式2:不使用匿名函数
setInterval(函数名,毫秒)
function 函数名(){}
2 setTimeout(参数1,参数2)
等待xx毫秒执行一次该函数
参数1:函数
参数2:毫秒
3 停止计时器
clearInterval(定时器序列名)
递归
在一个方法中,调用本方法
public static void xx(int i){
System.out.println("ok");
i++;
if(i<=5){
xx(i);
}
}
public static void main(String[] args) {
xx(1);
}
// 通过递归完成斐波那契排列
//1 1 2 3 5 8 13 21 34
public static void xx(int[]arr,int i){
arr[i+2]=arr[i]+arr[i+1];
i++;
if(i<=97){
xx(arr, i);
}
}
public static void main(String[] args) {
int [] arr=new int[100];
arr[0]=1;
arr[1]=1;
xx(arr, 0);
System.out.println(arr[7]);
}
全局变量
1 如果在onload中定义的变量都会自动提升位全局变量(因为变量指向window的的属性)
2 如果在函数中定义变量时不写var,则自动提升为全局变量
节点
我们会把页面上的所有元素当成节点来进行操作
元素(标签、属性、注释)
核心:根据层次关系来获取对应的元素
层次关系:
找爸爸
找儿子
找兄弟
节点的属性
1 nodeType 节点类型
在html中有12种节点,我们只需要直到标签节点/元素节点的type=1
文本节点的type=3
标签节点/元素节点: <span></span> <br/>
元素节点:标签外部的文本比如 xxxxx<span>xxxxx</span>xxxxx
2 firstElementChild | lastElementChild
找当前标签元素的第一个/最后一个元素节点儿子
3 children 获得当前节点的所有元素子节点
console.log(d.children[0]);
console.log(d.children[d.children.length - 1]);
4 直接获得html标签对象
document.documentElement
5 直接获得body标签对象
document.body
6 找当前元素节点的下一个元素兄弟
nextElementSibling
7 找当前元素节点的上一个元素兄弟
previousElementSibling
8 找当前元素节点的父节点
parentNode
节点方法
1 操作属性
节点对象.setAttribute("属性",“值”)
节点对象.getAttribute("属性")
2 创建节点
var 节点= document.createElement("标签名")
3 父节点追加字节点到末尾
父节点.appendChild(子节点(新节点));
4 删除当前节点
当前节点的父节点.removeChild(当前节点)