JS笔记-强化版1
Posted 钢铁小坦克
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JS笔记-强化版1相关的知识,希望对你有一定的参考价值。
1、函数:可以理解为-命令,做一些事~~
function abc(){ // 肯定不会主动执行的!
……
}
直接调用:abc();
事件调用:元素.事件 = 函数名 oDiv.onclick = abc;
function (){} 匿名函数
元素.事件 = function (){};
<script></script>写在<head>中要用window.onload来执行:
window.onload = function(){ ... js code... }
<script></script>写在执行标签后面可以用闭包来执行:
(function(){ ... js code ... })();
2、属性读写操作注意事项:
1)JS中不允许出现“-”横杠。JS修改css样式时出现类似font-size的属性写成驼峰名:p1.style.fontSize = num;
2)修改元素的class:p1.className = ‘red‘; class为保留字,JS中不能出现,修改class要调用className。 class = className.
3)不要拿相对路径做if判断
4)不要拿颜色值做判断
5)不要拿innerhtml做判断
6)表单元素type值修改,IE678不兼容
7)float的兼容:IE(styleFloat) 非IE(cssFloat) oDiv.style.styleFloat = "left";
8)JS中的 . 后面的值是无法修改的。变量或者参数要用[ ];
若是个变量则用[ ] 例如oDiv.style[oAttr.value] = ..; oDiv.style.width = oDiv.style[‘width‘]
3、document.getElementById与getElementsByTagName的区别:
1)ById是静态方法,前面只能是document;ByTagName前面也可以是别的元素
2)TagName获取的是一堆元素的集合。获取集合中的元素 要用[ ].比如获取第一个ul:document.getElementsByTagName(‘ul‘)[0];
如果代码中只有一个标签,用tagname获取的时候后面必须要加 [0] 。
3)如果在TagName后面用 JS动态增加一些标签,TagName可以获取到而ById获取不到。
修改title或者body:document.title =../document.body.innerHTML = ..
其中document.body.innerHTML =...; 是个简单的替换 不是添加。添加多行标签用+=:document.body.innerHTML +=...;
4、for循环
for(i=0;i<3;i++){alert(i);} 弹出 0 1 2;若在循环体外面在执行一次alert(i); 则弹出 3;
对一堆button进行点击事件可用for循环:for(i=0;i<oDiv.length;i++){oDiv[i].onclick=function(){...}}
循环增加一堆标签:
var str = ‘‘;
for( var i=0; i<6000; i++ ){
// document.body.innerHTML += ‘<input type="button" value="按钮" />‘;//有性能问题
str += ‘<input type="button" value="按钮" />‘;
}
document.body.innerHTML = str;
5、for嵌套for:
<ul id=‘‘list‘‘>
<li><h2>我的好友</h2><ul><li>Mary</li><li>Mark</li><li>Lady</li></ul></li>
<li><h2>我的坏友</h2><ul><li>Lala</li><li>Haha</li><li>Mama</li></ul></li>
</ul>
JS给每一个好友添加边框:
var oUl = document.getElementById(‘list‘);
var aUl = oUl.getElementsByTagName(‘ul‘);
var len = aUl.length;
var aLi = null; // 空
for( var i=0; i<len; i++ ){
aLi = aUl[i].getElementsByTagName(‘li‘);
for( var j=0; j<aLi.length; j++ ){
aLi[j].style.border = ‘1px solid red‘;
}
}
6、cssText文本格式化:
如果元素本身有某种样式,cssText设置该元素此样式后会覆盖。
若没有某种样式,多个cssText设置此样式,后面的会覆盖前面cssText所设置的所有样式。
最后一个为空,则清空以上的cssText设置的内容。
7、this: 指的是调用 当前 方法(函数)的那个对象
function fn1(){
// this
}
// fn1(); //fn1里的this => window
// oDiv.onclick = fn1; // fn1里的this => oDiv
oDiv.onclick = function (){
this.style... 中 this =>oDiv
//fn1(); //fn1() 里的this => window
};
<div onclick=" this fn1(); "></div> fn1(); 里的 this 指的是 window
8、在使用含有多个参数的css样式时分开来写:
iBox[i].style.backgroundPositionX =‘-‘ + i%8*65 + ‘px‘;
iBox[i].style.backgroundPositionY =‘-‘ + 60 + ‘px‘;
//background-position:x y;中x,y为负数是指图片向左,上偏移了x和y px;
9、自定义属性:
var aLi = document.getElementsByTagName(‘li‘);
// var onOff = true; // 只能控制一组!
for( var i=0; i<aLi.length; i++ ){
aLi[i].onOff = true; //自定义属性 给每一个标签添加true属性
aLi[i].onclick = function (){
// 背景不能判断
// color red #f00
// 相对路径不能判断
if ( this.onOff ) {
this.style.background = ‘url(img/active.png)‘;
this.onOff = false;
} else {
this.style.background = ‘url(img/normal.png)‘;
this.onOff = true;
}
};
想建立匹配关系 就用索引值,也是自定义属性的一种。Btn[i].index=i;
10、JS中的数据类型:数字(NaN)、字符串、布尔、函数、对象(obj、[]、{}、null)、未定义
Number()
var a = ‘+100‘;
// alert( a+100 ); // ‘100100‘// alert( Number(a) ); // 100var a1 = ‘ ‘;// alert( Number(a1) ); // 0var a2 = true;// alert( Number(a2) ); // 1 (false-0)var a3 = [ 2 ];// alert( Number(a3) ); // 1var a3 = [ ];// alert( Number(a3) ); // 0var a4 = null;// alert( Number(a4) ); // 0var a3 = function (){ alert(1); };// alert( Number(a3) ); // NaNalert( Number(‘……‘) ); NaN
显式类型转换(强制类型转换):Number() parseInt() parseFloat()
隐式类型转换:
+ 200 + ‘3‘ 变成字符串- * / % ‘200‘ - 3 变成数字++ -- 变成数字> < 数字的比较 、字符串的比较 ‘10‘>9为ture;‘10‘>‘9‘为false! 取反 把右边的数据类型转成布尔值==
NaN:
var a = Number(‘abc‘);alert( a ); // NaNNaN:not a number 不是个 数字 的 数字类型alert( typeof (a) ); // number一旦写程序中出现:NaN 肯定进行了非法的运算操作NaN 是 falseNaN 与自己都不相等!isNaN();判断某些值是不是数字,是数字返回false
typeof:
typeof是一个一元运算符,它返回的结果始终是一个字符串,对不同的操作数,它返回不同的结果。具体的规则如下:一、对于数字类型的操作数而言, typeof 返回的值是 number。比如说:typeof(1),返回的值就是number。上面是举的常规数字,对于非常规的数字类型而言,其结果返回的也是number。比如typeof(NaN),NaN在javascript中代表的是特殊非数字值,虽然它本身是一个数字类型。在JavaScript中,特殊的数字类型还有几种:Infinity 表示无穷大特殊值NaN 特殊的非数字值Number.MAX_VALUE 可表示的最大数字Number.MIN_VALUE 可表示的最小数字(与零最接近)Number.NaN 特殊的非数字值Number.POSITIVE_INFINITY 表示正无穷大的特殊值Number.NEGATIVE_INFINITY 表示负无穷大的特殊值以上特殊类型,在用typeof进行运算进,其结果都将是number。二、对于字符串类型, typeof 返回的值是 string。比如typeof("123")返回的值是string。三、对于布尔类型, typeof 返回的值是 boolean 。比如typeof(true)返回的值是boolean。四、对于对象、数组、null 返回的值是 object 。比如typeof(window),typeof(document),typeof(null)返回的值都是object。五、对于函数类型,返回的值是 function。比如:typeof(eval),typeof(Date)返回的值都是function。六、如果运算数是没有定义的(比如说不存在的变量、函数或者undefined),将返回undefined。比如:typeof(sss)、typeof(undefined)都返回undefined。
11、函数传递参数:
参数=JS中的数据类型:数字(NaN)、字符串、布尔、函数、对象(obj、[]、{}、null)、未定义
重用代码:1)尽量保证 HTML 代码结构一致,可以通过父级选取子元素2)把核心主程序实现,用函数包起来3)把每组里不同的值找出来,通过传参实现
innerHTML后面的值都是字符串格式,如果把后面的值作为参数,进行数学运算的时候要转成数值格式Number()
或者用隐式类型转换格式
12、作用域:
域:空间、范围、区域……如 <script>、函数和json数据都会执行解析器基本的两个步骤
作用:读、写
script 全局变量、全局函数:
自上而下
函数:
可以由里到外得到变量 不能从外到里
JSON{}
浏览器解析:“JS解析器”
1)“找一些东西” :var function 参数
初始化 a = undefined所有的变量,在正式运行代码之前,都提前赋了一个值:未定义fn1 = function fn1(){ alert(2); }所有的函数,在正式运行代码之前,都是整个函数块JS 的预解析遇到重名的:只留一个变量和函数重名了,就只留下函数
2)逐行解读代码:
表达式:= + - * / % ++ -- ! 参数……
表达式可以修改预解析的值!
函数中的由上到下先找函数中变量的预解析值,找到变量的值就变为预解析值
如果预解析找不到var function 参数,则沿着作 用域链向父级寻找
eg1:
alert(a); // function a (){ alert(4); } // 最先弹出的是预解析内容var a = 1;alert(a); // 1function a (){ alert(2); }alert(a); // 1var a = 3;alert(a); // 3function a (){ alert(4); }alert(a); // 3alert( typeof a ) ; //number// a(); // 报错eg2:var a = 1;function fn1(){ //函数也会执行预解析和代码解读两个步骤alert(a); // undefined //函数中的a是局部的和外面的a不同,等于函数中的预解析值var a = 2;}fn1();alert(a); // 1 //<script>中的预解析值eg3:var a = 1;function fn1(){alert(a); // 1a = 2; // 函数中没有var、function、参数所以没有预解析值,则沿着作用域链向父级寻找 把父级a的预解析值改成了2}fn1();alert(a); // 2 //a已被函数改成了2eg4:var a = 1;function fn1(a){alert(a); // undefined 参数也会有预解析值 此时参数a = undefineda = 2;}fn1();//调用时没加参数alert(a); // 1eg5:var a = 1;function fn1(a){ // 参数在预解析中 a = undefined ,调用时参数为1alert(a); // 1a = 2;}fn1(a);调用时有参数 a=1alert(a); // 1eg6:函数可以由里到外访问变量,不能由外到里。若函数外面想要获取函数内的值:1)var str = ‘‘; //可以先定义一个空字符串function fn1(){var a = ‘大鸡腿~‘;str = a;}fn1();alert( str ); //大鸡腿~2)function fn2(){var a = ‘9999999克拉钻石23456789‘;fn3(a);}fn2();function fn3(a){alert(a); //9999999克拉钻石23456789}
eg7:
页面有三个按钮,在点击任意一个按钮的时候使三个按钮背景颜色变成黄色:
1)错误示范
var aBtn = document.getElementsByTagName(‘input‘);for( var i=0; i<aBtn.length; i++ ){aBtn[i].onclick = function (){
// alert( i );// 3。结果为3,因为for括号中的i已经为3,此时函数中的i访问父级中的i就为3
//而aBtn[i]就为aBtn[3],因为长度为三3,最后一个元素为2,因此访问不到
aBtn[i].style.background = ‘yellow‘;//此时不仅不会变颜色而且控制台会报错
};}
2)正确做法
var aBtn = document.getElementsByTagName(‘input‘);
for( var i=0; i<aBtn.length; i++ ){aBtn[i].onclick = function (){// alert( i ); // undefined。 函数中有i,此时会预解析,即初始预解析为undefinedfor( var i=0; i<aBtn.length; i++ ){ //如果里面的i不加var,则函数中就预解析不到i,因此向父级寻找i,即i=3;aBtn[i].style.background = ‘yellow‘;}};}
eg8:
alert( fn1 ); // 火狐 不能对下面的函数进行预解析if( true ){var a = 1;function fn1(){
alert(123);}
}推荐写法,把定义的变量和函数写在 if 外面。其中:alert(fn1); //把函数块的全部内容弹出来alert(fn1()); //调用fn1函数
13、真假的问题
真假的问题:数据类型-数字(NaN)、字符串、布尔、函数、对象(elem、[]、{}、null)、未定义
真:非0的数字、非空字符串、true、函数、能找到的元素、[]、{}
假:0、NaN、空字符串‘‘、false、不能找到的元素、null、未定义
14、return
return 返回值:数字、字符串、布尔、函数、对象(元素\[]\{}\null)、未定义
1) 函数名+括号:fn1() 返回 return 后面的值;像类似点击事件的调用函数就不要加括号:div.onclick = fn;
2) 所有函数默认返回值:未定义;return后面为空,未定义。
3) return 后面任何代码都不执行了;
function fn1( ){
return function ( ){
alert(1);
};
}
fn(); //函数调用就是执行return后的值。如果是return值为函数,alert会把函数块整体弹出来。
fn()(); //弹出 1,两个括号执行return返回的函数。
function fn2(a){
return function (b){
alert(a+b);
};
}
fn2(20)(10); //执行 a+b 结果为30
15、arguments对象:
只在函数中可以使用的:return、arguments、this、event ```等、
fn1( 1,2,3 ); // 实参——实际传递的参数
// function fn1( a,b,c ){ // 形参——形式上,abc这些名代表123
function fn1(){ // 若函数没有形参,arguments会接收实参集合。
// arguments => [ 1,2,3 ] —— 实参的集合
// alert( arguments ); //object arguments
// alert( arguments.length ); //3
// alert( arguments[arguments.length-2] ); // 2
}
当函数的参数个数无法确定的时候:用 arguments
eg:
alert( sum( 1,2,3 ) ); // 6alert( sum( 1,2,3,4 ) ); // 10function sum (){var n = 0;
for( var i=0; i<arguments.length; i++ ){
n += arguments[i];
}
return n;}
arguments与作用域:
var a = 1;
function fn2( a ){
arguments[0] = 3; // 实参为1,存在arguments[0]中,此时变为3
alert(a); // 3
var a = 2; //修改局部的a值为2;a就是arguments[0];
alert( arguments[0] ); // 2
}
fn2(a);
alert(a); // 1 //最上面定义的a的值,与函数内部的a无关。
16、getComputedStyle和currentStyle获取元素样式
getComputedStyle IE 7 8 不兼容
currentStyle 标准浏览器不兼容
兼容性处理:
假设$(‘div1‘)为元素被获取后的代码
if( $(‘div1‘).currentStyle ){
alert( $(‘div1‘).currentStyle.width );
} else {
alert( getComputedStyle( $(‘div1‘)).width );
}
这两个获取到的是计算机(浏览器)计算后的样式
background: url() red …… 复合样式(不要获取)
backgroundColor 单一样式(可以获取,但不要用来做判断)
不要有空格
不要获取未设置后的样式:不兼容
兼容函数:
function getStyle(obj,attr){
return obj.currentStyle?obj.currentStyle[attr]:getComputedStyle(obj)[attr];
}
17、定时器
var timer = setInterval( 函数, 毫秒 ); 重复执行(发动机) 函数不要加括号否则直接执行:setInterval( fn1, 200 );
clearInterval( timer ); 清除
var timer = setTimeout( 函数, 毫秒 ); 执行一次(炸弹)
clearTimeout( timer );
*/
// for(var i=0; i<3; i++){ document.title = i; } // 瞬间完成,没有时间根据
有些参数不能传参,比如大于小于号,那么就在函数中进行判断。
函数封装后,需要添加回调函数:
eg:doMove ( oDiv, ‘left‘, 12, 900, function () {
doMove ( oDiv, ‘top‘, 34, 500 );
});
//函数:
function doMove ( obj, attr, dir, target, endFn ) {
....
if ( endFn ) {endFn(); } //等价于:endFn && endFn();
}
18、时间对象:
new Date();//当前系统的时间对象
时钟:
var myTime = new Date();
var iYear = myTime.getFullYear();var iMonth = myTime.getMonth()+1;var iDate = myTime.getDate();//日var iWeek = myTime.getDay();//数字0-6var iHours = myTime.getHours();var iMin = myTime.getMinutes();var iSec = myTime.getSeconds();
倒计时:
var iNow = new Date();
// var iNew = new Date( 2013, 10, 27, 21,56,0 );//数字形式var iNew = new Date( ‘November 27,2013 22:3:0‘ );//字符串形式var t = Math.floor((iNew - iNow)/1000);// 毫秒 - 秒var str = Math.floor(t/86400)+‘天‘+Math.floor(t%86400/3600)+‘时‘+Math.floor(t%86400%3600/60)+‘分‘+t%60+‘秒‘;alert( str );// 天:Math.floor(t/86400)// 时:Math.floor(t%86400/3600)// 分:Math.floor(t%86400%3600/60)// 秒:t%60
19、字符串方法:
var str = ‘miaomiaomiaomiao‘;
1) str.length;包括空格
2) str.charAt();括号里是索引值,取值范围:0-str.length-1;默认是0,第一个字符,超出范围, alert为空
3) str.charCodeAt(); 括号里是索引值,取值范围0-str.length-1,输出字符的编码;
4) String.fromCharCode();输入字符编码,可填写多个,输出字符。0~9 48-57; a~z 97-122; A-Z 65-90
5) str.indexOf(‘m‘,4);从第四位开始找m。第一个参数为字符串,输出字符串第一个字符的索引值。第二个参数可选。
默认从 第一位开始找。找不到返回 -1。
6)str.lastIndexOf();从右往左找。如果第二个参数为负数,默认当成0来处理。
7) 字符串用大于或小于比较,比较的是各字符串的第一个字符的编码大小。
8) str.substring(4);从索引值为4,即第五个开始截取到最后,包括第五个。
str.substring(0,2);从第一个开始截取到第二个,不包括第三个 。左闭右开 [0,2 )
str.substring(2,0);与substring(0,2)一样,两个参数会从小的开始截取到大的位置。
如果参数有负数,则当成0来处理。
9) str.slice(0,2);截取0-1两个位置,不包括索引值为2的位置,第一个参数要比第二个小。
str.slice(-2);从右往左找两个。
str.slice(-4,-2);-1 指最后一个元素,-2 指倒数第二个元素。所以截取倒数第四、第五个元素
10) str.toUpperCase(); 全部转化成大写
11) str.toLowerCase(); 全部转化成小写
12) ‘maolove.cn‘.split(‘ . ‘);把字符串根据小数点分割成数组 [‘maolove‘,‘cn‘]
‘leo‘.split()不放分隔符,将整体当成数组一个值。//[‘leo‘]
‘leo‘.split(‘‘);//[‘l‘,‘e‘,‘o‘]
‘/www.baidu.com/‘.split(‘/‘); [‘‘,‘www.baidu.com‘,‘‘]
‘2016-08-17-15-20-00‘.split(‘-‘,3); 分割前三段 [‘2016‘,‘08‘,‘17‘]
20、JSON
var json = { ‘name‘ : ‘leo‘, ‘age‘ : 32 }; //name 和 age 最好加引号
alert( json.name );// 或者通过 json[‘name‘]来获取。
可放数组:
var imgData = {
url : [ ‘img/1.png‘, ‘img/2.png‘, ‘img/3.png‘, ‘img/4.png‘ ],
text : [ ‘小宠物‘, ‘图片二‘, ‘图片三‘, ‘面具‘ ]
};
alert( imgData.url[2] );//img/3.png
数组里可以放json,json里也可以放数组
var arr = [ { ‘name‘ : ‘TM‘, ‘age‘ : 23 }, { ‘name‘ : ‘leo‘, ‘age‘ : 32 } ];
alert( arr[0].name ); // TM
json的遍历:
json没有length属性,不能用for循环来遍历,要用 for in
var json4 = { ‘name‘ : ‘miaov‘, ‘age‘ : 3, ‘fun‘ : ‘前端开发‘ };
for ( var attr in json4 ) {
alert( attr ); // name age fun
alert( json4[attr] ); // miaov 3 前端开发
alert(json4[‘attr‘]); // undefined undefined undefined
}
含有数组json 的遍历:
var json5 = {
‘url‘ : [ ‘img/1.png‘, ‘img/2.png‘, ‘img/3.png‘, ‘img/4.png‘ ],
‘text‘ : [ ‘小宠物‘, ‘图片二‘, ‘图片三‘, ‘面具‘ ]
};
for ( var attr in json5 ) {
for ( var i=0; i < json5[attr].length; i++ ) {
alert( json5[attr][i] );
}
}
21、for in 遍历对象属性
var str = ‘‘;
var num = 0;
for ( var attr in document ) {
str += num + ‘. ‘ + attr + ‘:‘ +document[attr] + ‘<br />‘;
num ++;
}
document.body.innerHTML = str;
// 1. 2. 3. ...
也可以对数组使用 for in遍历
22、数组
var arr = [ 1,2,3 ];
var arr = new Array(1,2,3);
alert( arr ); //两种定义数组方法
var arr = new Array(14);
alert( arr.length );// 14
var arr = new Array(‘3‘);
alert( arr.length );// 1
var arr = [ ‘aaa‘,2,3 ];
改变数组:
arr.length = 1;
alert(arr.length);// aaa
清空数组:
arr.length = 0;
arr = [];
字符串中的 str.length只能读不可写
数组添加方法:push、unshift
arr.push() ; 往数组后面追加;括号内添写添加的内容。
arr.unshift();往数组前面添加;括号内添写添加的内容;不支持IE6、7
数组删除方法:pop、shift
arr.pop();删除数组最后一个;括号内不添加参数;返回值是被删除的内容。
arr.shift();删除数组第一个;括号内不添加参数;返回值是被删除的内容。
技巧:
arr.unshift(arr.pop()); 后面的往前排
arr.push(arr.shift());前面的往后排
splice:
var arr = [ ‘TM‘, ‘钟毅‘, ‘张森‘, ‘杜鹏‘, ‘Leo‘ ];
删除:
arr.splice( 1, 2 );//删除了第二和第三个,第一个为开始索引位置,第二个参数为个数
替换:
arr.splice( 0, 2, ‘哈哈‘ );//将前两个替换成一个哈哈
添加:
arr.splice( 1, 0, ‘钟毅媳妇儿~‘, ‘钟毅媳妇们~‘ );
//第二个参数为0,则没有删除的个数,在第二个位置前,添加了两个字符串
alert( arr );
alert( arr.splice( 1, 0, ‘aaaaaa‘ ) );//只返回删除的值
join:
join() 方法用于把数组中的所有元素放入一个字符串。
元素是通过指定的分隔符进行分隔的。
与字符串的split方法相反,split是将字符串根据分隔符转成数组形式。
数组去重:
var arr = [ 1,2,2,4,2 ];
for ( var i=0; i<arr.length; i++ ) {
for ( var j=i+1; j<arr.length; j++ ) {
if ( arr[i] == arr[j] ) {
arr.splice( j, 1 );
j--;
}
}
}
alert( arr );
sort:
var arr2 = [ 4,3,5,5,76,2,0,8 ];
arr2.sort(); // 从小到大排序,8最大,原因是sort默认是排序字符串的。比较第一个字符的编码,8最大。
alert( arr2 );
从小到大:
arr2.sort(function ( a, b ) {
return a - b;
});
从大到小:
arr2.sort(function ( a, b ) {
return b - a;
});
eg:
var arrWidth = [ ‘345px‘, ‘23px‘, ‘10px‘, ‘1000px‘ ];
arrWidth.sort(function ( a, b ) {
return parseInt(a) - parseInt(b); //只会拿来颠倒位置,不会改变数组内容。
});
随机排序:Math.random(); //0 - 1
var arr = [ 1,2,3,4,5,6,7,8 ];
arr.sort(function ( a, b ) {
return Math.random() - 0.5;
});
alert( arr );
四舍五入: Math.round()
向上取整:Math.ceil()
向下取整:Math.floor();
0-10间的整数: Math.round(Math.random()*10);
20-30间的整数: Math.round(Math.random()*10+20);
x-y间的整数:Math.round( Math.random()*(y-x) + x ) ;
0-x间的整数:Math.round( Math.random()* x ) ;
1-x间的整数:Math.ceil( Math.random()*x) ;//向上取整
concat:合并数组
var arr1 = [ 1,2,3 ];
var arr2 = [ 4,5,6 ];
var arr3 = [ 7,8,9 ];
alert( arr1.concat( arr2, arr3 ) );//[1,2,3,4,5,6,7,8,9]
reverse:反转数组,属于数组的方法,不能直接反转字符串
数组内容反转:
var arr1 = [ 1,2,3,4,5,6 ];
arr1.reverse();//6 5 4 3 2 1
把字符串内容反转:
var str = ‘abcdef‘;
str.split(‘‘).reverse().join(‘‘);//先把字符串分割成数组后反转,之后将数组还原成字符串
以上是关于JS笔记-强化版1的主要内容,如果未能解决你的问题,请参考以下文章
《Effective Java 中文版 第2版》学习笔记 第4条:通过私有构造器强化不可实例化的能力