首页 文章 精选 留言 我的

精选列表

搜索[javascript库],共10000篇文章
优秀的个人博客,低调大师

javascript对点击事件和拖动事件的区分

1.项目中,为了更好的服务用户,经常会设计一个便捷的通道,这个通道一般都是“悬浮”的。 由于是悬浮的,那么就会考虑用户会出现哪几种可能的操作,一个是直接点击,另外一种就是在屏幕上先拖动几下,然后再点击。 那么为了完美的实现这个需求,那么该怎么办呢? 最重要的就是要区分点击事件和拖动事件。 我们都知道,点击事件是被点击的对象可看做是静止不动的,而拖动事件的对象很明显是移动的。 那么思路就应该是先判断事件对象是否有移动的现象, 但是由于不管是在点击事件和拖动事件,其都有一个鼠标按下的一个过程和一个松开的过程,只不过拖动事件多了一个拖动动作。 那么完整的思路应是这样的: 第一:先写出鼠标按下的函数; 第二:编写对象是否被拖动的函数; 第三:判断对象是否相对原先位置产生了位移; 第四:编写鼠标松开之后的代码; 完整代码如下: var timmerHandle = null; //先设置一个定时器处理; var isDrag = false; //声明拖动的默认状态是:否 //创建目标被点击(鼠标按下)的函数 function entranceDivDown (){ console.log("mouse down."); isDrag = false; //延迟100ms timmerHandle = setTimeout(setDragTrue,200); } function setDragTrue (){ isDrag = true; } //创建目标被拖动的函数 function entranceDivMove (){ //可以使用isDrag来判断是移动还是拖动 var entrance = document.getElementById("entrance"); entrance.onmousedown = function(ev){ var oevent = ev || event; var distanceX = oevent.clientX - entrance.offsetLeft; var distanceY = oevent.clientY - entrance.offsetTop; document.onmousemove = function(ev){ var oevent = ev || event; entrance.style.left = oevent.clientX - distanceX + 'px'; entrance.style.top = oevent.clientY - distanceY + 'px'; }; document.onmouseup = function(ev){ document.onmousemove = null; document.onmouseup = null; }; }; } //创建目标鼠标释放的函数 function entranceDivUp (){ if (!isDrag){ //先把doMouseDownTimmer清除,不然200毫秒后setGragTrue方法还是会被调用的 clearTimeout(timmerHandle); console.log("mouse up."); activity_id = sessionStorage.getItem('activity_id'); //获取活动id var url = globalConfig.pre_api_url + "/wall/index.php?activity_id=" + activity_id; //活动路径 window.open(url); }else{ isDrag = false; console.log("draging over."); } } 感谢阅读 喜欢看小编文章的点个订阅或者喜欢!小编每天都会跟大家分享文章,也会给大家提供web前端学习资料。

优秀的个人博客,低调大师

JavaScript tips —— 关于下载与导出的二三事

前言 在项目中经常会遇到下载或导出服务端资源的需求,一般分为2种做法 获取文件流,编码后下载 获取文件的url,直接下载 本文主要探讨第二种方法,在最后会提及文件流的方法。 浏览器的安全策略 在介绍方法之前,我们需要知道浏览器的一些安全机制,防止恶意代码对用户的破坏。 现代浏览器(ie8除外)检测到非用户直接操作产生的新窗口,一般会阻止,比如在ajax的回调中打开新的窗口,因为这些操作并不是在用户点击的线程中,所以会拦截。 预开新标签页 做法 在异步操作之前,先打开一个新标签页 请求后端资源的地址 获取url后去修改空白页的url const downloadTab = window.open('about:blank'); ajax.get('xxx').then(url => { // 使用后端资源的url进行下载 downloadTab.location.href = href; }).catch(err => { // 处理异常 downloadTab.close(); }) 缺点 不管请求成功还是失败都会有新页面的闪烁出现 打开的新页面在什么时候关闭是个问题,因为如果请求时间过长,用户可能自己关闭新页面,更不好处理的情况是页面什么时候触发了下载,因为如果只是更改url就关闭窗口可能还没有开始下载的操作。 生成iframe 做法 为了避免页面闪烁与关闭时机的问题,可以在当前页面使用动态创建iframe的方式,直接下载 ajax.get('xxx').then(href => { if (!href) { return; } if (!this.downIframe) { this.downIframe = document.createElement('iframe'); // 动态创建iframe this.downIframe.src = href; // iframe 中加载的页面 this.downIframe.id = 'downloadIframe'; // iframe 中加载的页面 this.downIframe.style.width = '1px'; this.downIframe.style.height = '1px'; this.downIframe.style.position = 'absolute'; this.downIframe.style.left = '-100px'; document.body.appendChild(this.downIframe); // 添加到当前窗体 } else { this.downIframe.src = href; // iframe 中加载的页面 } }).catch(err => { // 处理异常 }) 缺点 虽然可以优雅的下载文件了,可是如果需要定制下载文件的名称就会令人头疼了,我们需要时机去改变下载文件的名称,否则就会使服务端的文件名称。 生成标签 利用download属性设置文件名, ajax.get('xxx').then(href => { if (!href) { return; } var a = document.createElement('a'); var url = href; var filename = 'test.zip'; a.href = url; a.download = filename; // 在没有download属性的情况,target="_blank",仍会阻止打开 a.click(); }).catch(err => { // 处理异常 }) 注意: 有些浏览器需要将a标签嵌入页面才可执行。 处理文件流 最后大概讲一下如果后端返回流怎么处理。 function funDownload(content, filename) { // 创建隐藏的可下载链接 var link = document.createElement('a'); link.download = filename; link.style.display = 'none'; // 字符内容转变成blob地址 var blob = new Blob([content]); //如果是excel格式 //var blob = new Blob([content], {type: 'application/vnd.ms-excel'}), link.href = URL.createObjectURL(blob); // 触发点击 document.body.appendChild(link); link.click(); // 然后移除 document.body.removeChild(link); }; URL.createObjectURL() 静态方法会创建一个 DOMString,其中包含一个表示参数中给出的对象的URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的URL 对象表示指定的 File 对象或 Blob 对象。 注意: 在每次调用 createObjectURL() 方法时,都会创建一个新的 URL 对象,即使你已经用相同的对象作为参数创建过。当不再需要这些 URL 对象时,每个对象必须通过调用 URL.revokeObjectURL() 方法来释放。浏览器会在文档退出的时候自动释放它们,但是为了获得最佳性能和内存使用状况,你应该在安全的时机主动释放掉它们。 原文发布时间为:2018年06月15日 原文作者:nanchenk 本文来源:掘金如需转载请联系原作者

优秀的个人博客,低调大师

从零开始学 Web 之 JavaScript(四)数组

大家好,这里是「 Daotin的梦呓 」从零开始学 Web 系列教程。此文首发于「 Daotin的梦呓 」公众号,欢迎大家订阅关注。在这里我会从 Web 前端零基础开始,一步步学习 Web 相关的知识点,期间也会分享一些好玩的项目。现在就让我们一起进入 Web 前端学习的冒险之旅吧! 一、数组 1、数组定义 通过字面量定义数组: var arr = [10,20,30]; 通过构造函数定义数组: var arr = new Array(参数); // 参数位置为一个数值时为数组长度,多个数值时为数组中的元素。如果没有参数的时候 Array后面的括号可以省略。 2、数组操作 数组长度: 数组名.length; 问:数组中存储的数据类型一定是一样的吗? 类型可以不一样。 问:数组的长度是不是可以改变呢? 可以改变。 3、数组高级API 3.1、判断数组和转换数组 instanceof // 是一个关键字,判断A是否是B类型:A instanceof B。 isArray() //HTML5中新增 ,判断是不是数组 toString() //把数组转换成字符串,每一项用,分割 valueOf() //返回数组对象本身 join(变量) //根据每个字符把数组元素连起来变成字符串,变量可以有可以没有。不写变量默认用逗号分隔,无缝连接用空字符串。 instanceof var str1 = new String("abc"); var str2 = "abc"; console.log(str1 instanceof String); // true console.log(str2 instanceof String); // false str2不是String对象 join //join是把数组元素用特殊方式链接成字符串(参数决定用什么链接,无参默认用逗号链接) var arr = ["关羽","张飞","刘备"]; var str1 = arr.join(); var str2 = arr.join(" ");//如果用空格的话,那么元素之间会有一个空格 var str3 = arr.join("");//用空字符串,链接元素,无缝连接 var str4 = arr.join("&"); console.log(str1); console.log(str2); console.log(str3); console.log(str4); arguements 只在函数中使用,代表传入实参的数组。 arguements 是伪数组:不能修改长短的数组。(可以修改元素,但是不能变长变短) fn(1,2); fn(1,2,3); fn(1,2,3,4,5); function fn(a,b){ //只在函数中使用,实参的数组。 arguments[0] = 0; // 可以修改内容 console.log(arguments); //伪数组:不能修改长短的数组。(可以修改元素,但是不能变长变短) arguments.push(1); // arguments 伪数组没有push方法 console.log(arguments instanceof Array); // false //形参个数 console.log(fn.length); //实参个数 console.log(arguments.length); // 形参和实参个数可以不同,因为实参传入的时候可以形参的个数不一样。 // arguments.callee相当于函数名,这里打印整个函数。 console.log(arguments.callee); } 3.2、数组增删和换位置(原数组将被修改) push() //在数组最后面插入项,返回数组的长度 //数组1改后的长度 = 数组1.push(元素1); pop() //取出数组中的最后一项,返回最后一项 //被删除的元素 = 数组1.pop(); unshift() //在数组最前面插入项,返回数组的长度 //数组1改后的长度 = 数组1.unshift(元素1); shift() //取出数组中的第一个元素,返回第一项 //被删除的元素 = 数组1.shift(); reverse() //翻转数组(原数组讲被反转,返回值也是被反转后的数组) //反转后的数组 = 数组1.reverse(); sort(); //给数组排序,返回排序后的数组。如何排序看参数。 //从小到大排序后的数组 = 数组1.sort(function(a,b){ // return a-b; //}); sort var arr2 = [7,6,15,4,13,2,1]; console.log(arr2); // 7,6,15,4,13,2,1 console.log(arr2.sort()); // 1,13,15,2,4,6,7 问题:只能通过第一位字母或数字的 unicode 编码进行排列。 解决:sort方法不稳定,设计的时候就是这么设计的,可以通过回调函数进行规则设置。 console.log(arr2); console.log(arr2.sort(function (a,b) { return a-b; //升序 //b-a:降序 })); 4、迭代方法 作用:代替 for 循环。 every every() 他的返回值是一个 boolean 类型值。而参数是一个回调函数。 参数有三个。名字随便起,但是表示的意思还是这样顺序的。 var arr = ["关长","张飞","赵子龙","马超","黄忠"]; // function (element,index,array) // element:数组元素的值 // index:索引 // array:调用这个方法的整个数组对象(一般不用) arr.every(function(fff,www,ggg) { console.log(fff); console.log(www); console.log(ggg); return true; }); // 关长 // 0 // ["关长", "张飞", "赵龙", "马超", "黄忠"] // 张飞 // 1 // ["关长", "张飞", "赵龙", "马超", "黄忠"] // 赵龙 // 2 // ["关长", "张飞", "赵龙", "马超", "黄忠"] // 马超 // 3 // ["关长", "张飞", "赵龙", "马超", "黄忠"] // 黄忠 // 4 // ["关长", "张飞", "赵龙", "马超", "黄忠"] var arr = ["青花瓷", "一路向北", "轨迹"]; var flag = arr.every(function (ele, index) { // 只要有一个没满足条件,就返回false ele.length > 2; }); console.log(flag); // false filter filter 返回值是一个新数组。return 为 true 的数组。 var arr = ["关长","张飞","赵子龙","马超","黄忠"]; var arr1 = arr.filter(function (ele,index,array) { if(ele.length>2){ return true; } return false; }); console.log(arr1); // ["赵子龙"] foreach foreach 遍历数组(无返回值,纯操作数组中的元素) var arr = ["关长","张飞","赵子龙","马超","黄忠"]; var str = ""; arr.forEach(function (ele,index,array) { str+=ele; }); console.log(str); // 关长张飞赵子龙马超黄忠 map map有返回值,返回什么都添加到新数组中。 var arr = ["关长","张飞","赵子龙","马超","黄忠"]; var arr2 = arr.map(function (ele,index,array) { return ele+"你好"; }) console.log(arr2); // (5) ["关长你好", "张飞你好", "赵子龙你好", "马超你好", "黄忠你好"] some some有返回值,如果函数结果有一个是true,那么some方法结果也是true。 var arr = ["关长","张飞","赵子龙","马超","黄忠"]; var flag = arr.some(function (ele,index,array) { if(ele.length>2){ return true; } return false; }) console.log(flag); // true push 向数组的末尾添加一个或更多元素,并返回新的长度。 注意: 新元素将添加在数组的末尾。 注意: 此方法改变数组的长度。 提示: 在数组起始位置添加元素请使用 unshift() 方法。 pop 删除数组的最后一个元素并返回删除的元素。 注意:此方法改变数组的长度! 提示: 移除数组第一个元素,请使用 shift() 方法。 了解方法 concat() //把参数拼接到当前数组 //新数组 = 数组1.concat(数组2); slice() //从当前数组中截取一个新的数组,不影响原来的数组,参数start从0开始,end从1开始 //新数组 = 数组1.slice(索引1,索引2); splice()//删除或替换当前数组的某些项目,参数start,deleteCount,options(要替换的项目) //新数组 = 数组1.splice(起始索引,结束索引,替换内容); indexOf()、lastIndexOf() //如果没找到返回-1 //索引值 = 数组.indexOf/lastIndexOf(数组中的元素); forEach() // 数组每个元素都执行一次回调函数。 5、清空数组 var array = [1,2,3,4,5,6]; // 方法一:删除数组中所有项目 array.splice(0,array.length); // 方法二:length属性可以赋值,其它语言中length是只读 array.length = 0; // 方法三: array = []; //推荐

优秀的个人博客,低调大师

从零开始学 Web 之 JavaScript(三)函数

大家好,这里是「 Daotin的梦呓 」从零开始学 Web 系列教程。此文首发于「 Daotin的梦呓 」公众号,欢迎大家订阅关注。在这里我会从 Web 前端零基础开始,一步步学习 Web 相关的知识点,期间也会分享一些好玩的项目。现在就让我们一起进入 Web 前端学习的冒险之旅吧! 一、函数 1、函数的定义 // 第一种 function fn1(){ console.log("我是第一种定义方法!"); } // 第二种 var fn2 = function (){ console.log("我是第二种定义方法!"); }; // 注意分号 function (){ console.log("我是第二种定义方法!"); }(); // 第二种方式的调用方式之一:函数的自调用 //第三种 var fn3 = new Function("console.log('我是第三种定义方法!')"); 第一种:(函数的声明)第一种定义方法最强大,定义完毕后,在哪里使用都可以,无位置限制。第二种:(函数表达式:匿名函数) 后两种定义方法是有局限性的。(使用函数必须在定义函数之后) 2、函数的调用 函数名(); 3、函数名 要遵循驼峰命名法。 不能同名(函数重载),否则后面的函数会覆盖前面的函数。 //打印函数名,就等于打印整个函数。 console.log(fn); //打印执行函数,就等于打印函数的返回值。 console.log(fn()); 4、形参和实参 形参不需要写 var. 形参的个数和实参的个数可以不一致 。 5、返回值 如果函数没有显示的使用 return 语句 ,那么函数有默认的返回值:undefined 如果函数使用 return 语句,但是 return 后面没有任何值,那么函数的返回值也是:undefined. 6、变量和作用域 全局变量:在 script 使用 var 定义的变量(所有的 script 共享其全局性,js 里面没有块级作用域概念,只有全局作用域和局部作用域)。 隐式全局变量:在 script 没有 var 的变量。 function fn(){ var a = b = c = 1; // b和c就是隐式全局变量(等号) var a = 1; b = 2; c = 3; // b和c就是隐式全局变量(分号) var a = 1 , b = 2 , c = 3; // b和c就不是隐式全局变量(逗号) } (全局变量是不能被删除的,隐式全局变量是可以被删除的) var num1 = 10; num = 20; delete num1; delete num2; console.log(typeof num1); // number console.log(typeof num2); // undefined 局部变量:函数内部用 var 定义的变量和形参。 6.1、变量声明提升(预解析) 作用:查看语法错误。js的解析器在页面加载的时候,首先检查页面上的语法错误。把变量声明提升起来。(变量声明提升和函数整体提升) 6.2、变量的提升 只提升变量名,不提升变量值。 consolas.log(aaa);// 打印的结果是 undefined ,因为只提升变量名,不提升变量值。 var aaa = 111; 在函数范围内,照样适用。 6.3、函数的提升 function 直接定义的方法:整体提升(上面说的第一种函数定义的方法). fn(); var aaa = 111; function fn(){ //变量声明提升在函数内部照样实用。 //函数的就近原则(局部变量作用域),打印的aaa不是111,而是 undefined。 console.log(aaa); // undefined var aaa = 222; } 预解析会分块: 多对的 script 标签中函数重名的话,预解析不会冲突。也就是预解析的作用域是每一个的 script 标签。 var先提升,function再提升: 示例: console.log(a); // 输出a函数体 function a() { console.log("aaaaa"); } var a = 1; console.log(a); // 输出1 打印第一个结果的时候,var会提升,之后 function 再提升,但是函数a和变量a重名,function的a在后面覆盖掉变量a,所以第一个输出 a 函数体. 第二个前面var a = 1;提升之后,这个位置就相当于只有 a = 1; 赋值,所以第二个打印1. 6.4、匿名函数 作用大致如下: //1.直接调用 (function (){ console.log(1); })(); //2.绑定事件 document.onclick = function () { alert(1); } //3.定时器 setInterval(function () { console.log(444); },1000);

优秀的个人博客,低调大师

从零开始学 Web 之 JavaScript(二)变量

大家好,这里是「 Daotin的梦呓 」从零开始学 Web 系列教程。此文首发于「 Daotin的梦呓 」公众号,欢迎大家订阅关注。在这里我会从 Web 前端零基础开始,一步步学习 Web 相关的知识点,期间也会分享一些好玩的项目。现在就让我们一起进入 Web 前端学习的冒险之旅吧! 一、变量 1、变量的声明和初始化 var number; // 变量的声明,没有赋值 var name = "Daotin"; // 变量的初始化 2、命名规则(驼峰命名) 变量命名必须以字母或是**下标符号 "_" 或者 "$" 为开头。 变量名长度不能超过 255 个字符。 变量名中不允许使用空格,首个字不能为数字。 不用使用脚本语言中保留的关键字及保留符号作为变量名。 变量名区分大小写。 汉语可以作为变量名。但是不建议使用! 3、数据类型 查看数据类型的方法:typeof name; 或 typeof(name); PS:null 的数据类型是 Object。 基础数据类型:String,Number,Boolean,unsigned、null 复杂数据类型:Object,function,Array,Date,Error 等 4、Number 4.1、进制 var num = 10; // 十进制 var num = 012; // 八进制:0开头 var num = 0xA; // 十六进制:0x开头 4.2、数值范围 最小值: Number.MIN_VALUE ,这个值为: 5e-324 最大值: Number.MAX_VALUE ,这个值为: 1.7976931348623157e+308 无穷大: Infinity无穷小: -Infinity 4.3、不要用小数去验证小数 var num1 = 0.1; var num2 = 0.2; console.log(num1+num2); // 0.30000000000000004 console.log(0.07*100); // 7.000000000000001 4.4、NaN NaN:(Not a Number)本来应该得到一个数值的,但是结果却并不是一个数值。 console.log("abc"/18); // 结果是NaN 1、undefined 和任何数值计算为 NaN; 2、NaN 与任何值都不相等,包括 NaN 本身。 3、isNaN(); 任何不能被转换为数值的值都会导致这个函数返回 true。 5、String 使用单引号或者双引号均可。 获取字符串的长度使用 变量名.length 无法输出的字符,记得使用转义字符(\t , \, ", \ 等) 字符串拼接可以使用 + ,像在 Java 一样。 当一个是字符串,另一个是数字,并且使用 乘,减,除 号的时候,字符串会转换成数字进行计算,转换失败返回NaN。 js 没有字符类型只有字符串类型,字符串使用 "" 或者 '' 都是可以的。 字符串是常量不可变的。 示例: var str = "hello"; str[0] = "w"; console.log(str); // 还是 hello var str = "hello"; str = "world"; console.log(str); // 是 world,这个不是改变了当前str地址的字符串的值,而是str指向了新的字符串,旧的字符串的值仍然没有更改。 6、Boolean Boolean类型只有两个字面量: true 和 false 。但是所有类型均有与这两个 Boolean 值等价的值。 下面类型为 true:true、除0数字、"something"、Object(任何对象)下面类型为 false:false、0 、""(空字符串)、undefined 、null、NaN 7、undefined 与 null 虽然 undefined 和 null 都为 false,但是他们的区别是: 在进行数字运算的时候,null + 10 = 10;undefined + 10 = NaN. 任何数据类型和 undefined 运算都是 NaN; 任何值和 null 运算,null 可看做 0 运算。 8、数据类型转换 8.1、其他类型转换成String 变量+"" 或者 变量+"其他变量" String(变量) 变量.toString(); // 注意:undefined 和 null 不可以 var bool = true; var num = 111; var aaa; var bbb = null; console.log(typeof(bool+"")); // string console.log(typeof(num+"")); // string console.log(typeof(aaa+"")); // string console.log((aaa+"")); // undefined console.log(typeof(bbb+"")); // string 8.2、其他类型转换成 Number 此转换容易产生 NaN,一旦被转换的变量中含有非数字字符,都容易出现 NaN. **1. 变量 -*/ 一个数字(有非数字字符会出现 NaN)** var num1 = "11" - 0; var num2 = "11" * 1; var num = "11" / 1; 2. Number(变量);(有非数字字符会出现NaN) Number("11"); Number(""); // 空字符串返回 0 3. parseInt() 和 parseFloat()(译为取整和取浮点数) 空字符串:parseInt("") 和 parseFloat("") 返回 NaN。 parseInt(变量):如果变量中首字符为字母则结果为 NaN。否则取出现首个非数字前的整数。 123 = parseInt(“123.123aaaa”); parseFloat(变量):如果变量中首字符为字母则结果为 NaN。否则取出现首个非数字前的浮点数。(如果没有小数的话取整) 123.123 = parseFloat(“123.123aaaa”); PS: true 数值为 1;false 为0; null 的数值类型为 0; undefined 无数值类型或者为 NaN。 8.3、其他类型转换成 Boolean 任何数据类型都可以转换成 boolean 类型,所以和以上两个转换不同。将任意类型作为参数传入 Boolean(参数) 中,都可以转换成布尔值。 下面类型为 true:true、除0数字、"something"、Object(任何对象)下面类型为 false:false、0 、""(空字符串)、undefined 、null、NaN Boolean(参数);

资源下载

更多资源
Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册