javaScript
前言
javaScritp诞生于95年当时工作在Netscape(网景)公司的布兰登(Brendan Eich)为解决类似于“向服务器提交数据之前验证”的问题。在Netscape Navigator 2.0与Sun公司联手开发一个称之为LiveScript的脚本语言,经过发展,它成为一种面向对象型的解释型的语言。也是基于对象和事件驱动具有相对安全的性的客户端(浏览器)脚本语言。主要目的为了验证发往服务器端的数据,增加web互动,加强用户体验度。也有类似java封装继承的特点。
内置对象
- Global没有方法可以直接访问。web浏览器通常把它当作window对象,加以部分实现 并有undefined、NaN、Object、Array、Function属性
- encodeURIComponent()的编码方法
alert(decodeURIComponent(encodeURIComponent('//wang王')))
- eval() 强大又危险的字符串解析器
eval('var box = 100');//解析了字符串代码,同样可以注入是危险
alert(box);
eval('alert(100)');
eval('function box() {return 123}');//函数也可以
- data对象
//获取当前时间
var myDate = new Date();
var mytime=myDate.toLocaleTimeString(); //获取当前时间
myDate.toLocaleDateString(); //获取当前日期
myDate.toLocaleString( ); //获取日期与时间
- math对象 提供更快的计算操作
- min()和max() 获取一组数中的最小最大值
alert(Math.min(2,4,3,6,3,8,0,1,3));//最小值
alert(Math.max(4,7,8,3,1,9,6,0,3,2));//最大值
- Math.ceil(),Math.floor(),Math.round() 三种向上,向下,标准舍入
alert(Math.ceil(25.1)); //26
alert(Math.floor(25.9)); //25
alert(Math.round(25.9)); //26
alert(Math.round(25.5)); //26
alert(Math.round(25.1)); //25
- random()获取随机数方法
//获取指定范围的随机数
function getRandom(numStart,numEnd) {
return Math.floor(Math.random()*((numEnd-numStart)+1)+numStart) ;
}
//获取指定长度随机字符串
function getRandomStr(len) {
len = len || 4;
var chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; //去掉易混淆的字符
var maxPos = chars.length;
var random = '';
for(var i = 0; i<len; i++){
random += chars.charAt(Math.floor(Math.random()*maxPos))
}
return random;
}
- Math.abs(num) 返回num的绝对值
js中的this关键字和new 关键字
- js中没有块级作用域只有函数作用域,例如if,while,for代码块。如果没有通过var 声明属于window对象
function varscope(){
foo = "I'm in function";
console.log(foo);//I'm in function
}
varscope();
console.log(window.foo); //I'm in function 属于window对象
var obj={name:"hello",show:function(){
console.log(this.name);
}};
obj.show(); //hellow
var objA={name:"world"}
objA.show2=obj.show; //在调用的时候这个相当于把this 指向了方法ObjA,而不是obj。
objA.show() //word
使用 apply(作用域,参数数组)和call(作用域,参数列表) 改变this的指向(函数的执行环境)实现对象冒充和继承。每个函数都包含这两个非继承的方法。
//声明两个全局变量
window.firstName = "my";
window.lastName = "_obj";
//声明一个内部变量
var myObject = {firstName:'my', lastName:'test'};
//声明两个获取属性的方法
function getName(){
console.log(this.firstName + this.lastName);
}
function getMessage(sex,age){
console.log(this.firstName + this.lastName + " 性别: " + sex + " age: " + age );
}
//测试
getName.call(window); // my_obj
getName.call(myObject); // mytest
getName.apply(window); // my_obj
getName.apply(myObject);// mytest
getMessage.call(window,"女",21); //my_obj 性别: 女 age: 21
getMessage.apply(window,["女",21]); // my_obj 性别: 女 age: 21
getMessage.call(myObject,"未知",22); //mytest性别: 未知 age: 22
getMessage.apply(myObject,["未知",22]); // mytest性别: 未知 age: 22
//改变对象的作用域,把数据传递出去。
function mouseClick(A,fun) { //A 为插件,fun为函数类型
var data = {}; //创建一个MouseClick的对象
A.plugin(' ',function(){ //有些数据的处理可能必须在工具/插件内部作用域进行
//完成业务逻辑处理
....
....
fun.call(this,data); // 改变data的this指向
});
}
//调用
mouseClick(a,function (e) { //或者使用this得到data数据
//进行业务处理
});
- js 中的new关键字
在java中通过new 来实例对象,但在js中已经全部都是对象了,包括方法原型也是对象,new的本质其实是继承形成原型链。
原型对象(prototype)函数独有的属性,构造器原型(__ proto__)每个对象都有参考
在一个函数(函数链)中this 指向当前函数所有者的对象,在运行时确定具体的指向,具体的调用指向。
面向对象与原型原型参考
总结了4种单一的对象创建,和2种混合模式的创建。一般工厂模式和寄生模式,比较通用,像一些插件复杂的组件的编写一般是构造+原型的方式。
- 一个基本的对象的创建
var user = new Object(); //创建一个Object对象
user.name = 'Lee';
user.age = 100;
user.run = function () { //创建一个run()方法并返回值
return this.name + this.age + '运行中...';
}
//字面量
var user={
name:'爸爸',
age:'18'
run:function(){ }
}
ps: 缺点,代码并不能复用,创建另一个对象就要产生大量的代码
- 工厂模式
就是一个函数,然后放入参数,返回对象,解决了创建对象大量重复代码的问题
function createObject(name, age) { //集中实例化的函数
var obj = new Object();
obj.name = name;
obj.age = age;
obj.run = function () {
return this.name + this.age + '运行中...';
};
return obj;
}
ps: 调用的时候就像普通方法一样调用,缺点就是识别不了类型,工厂模式创建的对象都是object类型。有一定资源的消耗浪费
- 使用构造函数
构造函数方法名首字母约定大写
function Box(name, age) { //构造函数模式
this.name = name;
this.age = age;
this.run = function () {
return this.name + this.age + '运行中...';
};
}
var box = new Box();
image.png
ps:调用的时候通过new关键字进行实例,否则就是类似于工厂模式普通的方法。构造函数没有显示的 new Object()(后台进行),this就代表这个对象,且没有return语句。通过new 来调用的函数 会自动执行如下操作 创建一个全新的对象-这个对象会被执行[[prototype]]连接原型-函数调用中的this会绑定到新对象-如果函数没有返回其他对象,那么new 构造就会自动返回这个新对象
。所以构造函数可以box instanceof Box 确定实例类型,解决了工厂模式的弊端。但本质和工程模式没有什么区别而且实例调用方法的地址也都一样。
-
使用原型
我们创建的每一个函数都有一个prototype(原型)属性对象,它由原型通过调用构造函数创建。它可以使特定类型的所有实例共享属性和方法,这个对象有实例指向原型对象的指针___ proto___ 属性和指针指向构造函数的constructor两个属性。
image.png
//使用构造的原型
function Box() {} //声明一个构造函数
Box.prototype.name = 'Lee'; //在原型里添加属性
Box.prototype.age = 100;
Box.prototype.run = function () { //在原型里添加方法
return this.name + this.age + '运行中...';
};
//使用字面量的原型
function Box(){}; //声明构造函数
Box.prototype = {
name : 'Lee',
age : 100,
run : function () {
return this.name + this.age + '运行中...';
}
};
//字面量创建的方式使用constructor属性不会指向实例,而会指向Object,构造函数创建的方式则相反。
Box.prototype = {
constructor : Box, //直接强制指向实例
};
ps:因为原型方法属性都共享,有时候往往也很致命(如存在引用类型)会有导致数据的污染。还有方法命名冲突,且不利于维护的弊端。关于js的原型链其实就是有限的实例对象和原型之间组成有限链来实现共享属性和继承的。当查找属性时先查找构造函数实例里的属性或方法,如果有,立刻返回;如果构造函数实例里没有,则去它的原型对象里找,如果有,就返回否则就返回undefiend。
- 创建对象比较好的方法,使用混合模式
- 工厂模式+构造函数模式(通用)。也叫寄生构造函数
function Box(name, age) {
var obj = new Object();
obj.name = name;
obj.age = age;
obj.run = function () {
return this.name + this.age + '运行中...';
};
return obj;
}
var box = new Box('Lee'); //实例
alert(box.rung());
ps: 和工厂模式区别就是 通过new调用。使用场景:比如创建具有额外方法的已有类型(如数组,Date类型等),但是又不污染原有的类型。 所以就算没有new也可以得到对象(工厂模式),只不过加上new让人清楚这是新对象类型的实例,也是“寄生器构造函数”里有个“构造函数”的原因参考为什么要使用寄生
- 使用原型+构造。优秀的jquery插件大部分使用这种方式
function Box(name, age) { //不共享的使用构造函数
this.name = name;
this.age = age;
this. family = ['父亲', '母亲', '妹妹'];
};
Box.prototype = { //共享的使用原型模式
constructor : Box,
run : function () {
return this.name + this.age + this.family;
}
};
升级版 也叫动态原型模式
function Box(name ,age) { //将所有信息封装到函数体内
this.name = name;
this.age = age;
if (typeof this.run != 'function') { //仅在第一次调用的初始化
Box.prototype.run = function () {
return this.name + this.age + '运行中...';
};
}
}
var box = new Box('Lee', 100);
alert(box.run());
污染数据的问题如下
function Box() {};
Box.prototype = {
constructor : Box,
name : 'Lee',
age : 100,
family : ['父亲', '母亲', '妹妹'], //添加了一个数组属性
run : function () {
return this.name + this.age + this.family;
}
};
var box1 = new Box();
box1.family.push('哥哥'); //在实例中添加'哥哥'
alert(box1.run());
var box2 = new Box();
alert(box2.run()); //共享带来的麻烦,也有'哥哥'了
ps:这种‘原型+构造’方式是创建对象比较好的方法,解决了数据污染的问题。有很多优点,就是写的有点多。
继承
关于js继承是都是依靠原型链实现的。
- 基本的原型链继承把父类的实例作为子类的原型
具体查看
function Box() { //Box构造
this.name = 'Lee';
}
function Desk() { //Desk构造
this.age = 100;
}
Desk.prototype = new Box(); //Desc继承了Box,通过原型,形成链条
ps:无法向父类传参,来自原型对象的引用属性都是共享的
- 构造继承
使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类
function Box(age) {
this.name = ['Lee', 'Jack', 'Hello']
this.age = age;
}
function Desk(age) {
Box.call(this, age); //对象冒充,给父类型传参
}
vat desk = new Desk(100);
desk.name.push("hahah"); //只给desk添加新的数据
ps: 可以传递参数,也可以不共享,但不能使用父类的原型属性和方法复用也就有影响,每个子类都有父类实例的复制影响性能
- 组合继承
解决以上问题我们借用,原型链+借用构造函数的模式,通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
//定义父类
function Box(age) {
this.name = ['Lee', 'Jack', 'Hello']
this.age = age;
}
//父类的原型方法
Box.prototype.run = function () {
return this.name + this.age;
};
//子类
function Desk(age) {
Box.call(this, age); //对象冒充
}
Desk.prototype = new Box(); //原型链继承
var desk = new Desk(100);
alert(desk.run());
ps:这是原型继承和构造继承的所有优点,唯一的缺点调用了两次父类构造函数,生成了两份实例,子类实例覆盖了子类原型的实例
- 寄生式组合继承参考,组合模式和寄生组合模式区别
把原型式+工厂模式结合而来,封装创建对象的过程。
//采用寄生组合继承模式可以解决上述问题
function object(o) {
function F(){};
F.prototype = o;
return new F();
/*通过构造一个介于superType与subType之间的对象,并使该对象的prototype属性指向
superType prototype对象,来避开通过调用superType构造函数的方式来产生一个prototype
指向superType prototype对象的对象。
有点绕,好好理解*/
};
function inheritPrototype(Sub, Super) {
/*这里为何需要调用object函数去构造一个新的对象,而不直接让Sub.prototype=Super.prototype呢?
原因是如果这么做的话,当我们想给Sub的prototype里面添加共享属性或者方法时,如果其prototype指向的是Super的prototype,那么在Sub的prototype里添加的属性和方法也会反映在Super的prototype里面,这明显是不合理的,这样做的后果是当我们只想使用Super时,也能看见Sub往里面扔的方法和属性。所以需要每个构造函数都需要持有自己专用的prototype对象。*/
var prototype = object(Super.prototype);
prototype.constructor = Sub;
Sub.prototype = prototype;
};
function Super(name) {
this.name = name;
if(typeof Super.prototype.sayName != "function") {
Super.prototype.sayName = function () { console.log(this.name)};
};
}
function Sub(name, age) {
Super.call(this, name);
this.age = age;
if(typeof Super.prototype.sayAge != "function") {
Super.prototype.sayAge = function () { console.log(this.age)};
};
};
inheritPrototype(Sub, Super);
var sub = new Sub("bobo", 26);
sub.sayAge();
sub.sayName();
console.log("typeof sub:" + typeof sub);
delete sub.name;
sub.sayName();//undifined
function Animal (name) {
this.name = name || 'Animal';
this.sleep = function(){
console.log(this.name + '正在睡觉!');
}
}
Animal.prototype.eat = function(food) {
console.log(this.name + '正在吃:' + food);
};
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
Cat.prototype.constructor = Cat
}
(function(){
// 创建一个没有实例方法的类
var Super = function(){};
Super.prototype = Animal.prototype;
//将实例作为子类的原型
Cat.prototype = new Super();
})();
// Test Code
var cat = new Cat();
ps:解决了组合调用两次的问题,但是实现稍稍复杂
匿名函数的封装和闭包
匿名函数就是没有名字的函数,闭包是可访问一个函数作用域里变量的函数。匿名函数的作用是避免全局变量的污染以及函数名的冲突
- 匿名函数
//通过表达式自我执行
(function box() { //封装成表达式
alert('Lee');
})(); //()表示执行函数,并且传参
- 闭包
闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的常见的方式,就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量。使用闭包作用:访问局部变量,使变量常驻内存。优点可以避免使用全局变量,避免数据的污染。由于作用域链的关系局部变量资源不会被立刻销毁回收,所以可能会占用更多的内存。过度使用闭包会导致性能下降。
使用闭包访问内部变量
function f1(){
n=999;
function f2(){
alert(n);
}
//因为js链式作用域的结构,子对象会一级一级向上寻找父对象的变量,
对子对象都是可见的,反之不成立。把可以访问的f2作为f1的返回值,
就能实现访问内部的变量。
return f2;
}
var result=f1();
result(); // 999
通过闭包使变量常驻内存
function box() {
var age = 100;
return function () {
age ++;
return age;
}
}
var b = box(); //获得函数
alert(b()); //调用匿名函数
alert(b()); //第二次调用匿名函数,实现累加
闭包中this 指向window
var user = 'The Window';
var obj = {
user : 'The Object',
getUserFunction : function () {
return function () { //闭包不属于obj,里面的this指向window
return this.user;
};
}
};
alert(obj.getUserFunction()()); //The window
//可以强制指向某个对象
alert(obj.getUserFunction().call(obj)); //The Object
//也可以从上一个作用域中得到对象
getUserFunction : function () {
var that = this; //从对象的方法里得对象
return function () {
return that.user;
};
}
ie 中内存泄露的问题
function box() {
var oDiv = document.getElementById('oDiv'); //oDiv用完之后一直驻留在内存
oDiv.onclick = function () {
alert(oDiv.innerHTML); //这里用oDiv导致内存泄漏
};
}
box();
那么在最后应该将oDiv解除引用来避免内存泄漏。
function box() {
var oDiv = document.getElementById('oDiv');
var text = oDiv.innerHTML;
oDiv.onclick = function () {
alert(text);
};
oDiv = null; //解除引用
}
ps:如果并没有使用解除引用,那么需要等到浏览器关闭才得以释放
实现块级作用域,进行私有化
function box(count) {
for (var i=0; i<count; i++) {}
alert(i); //i不会因为离开了for块就失效
}
//私有化变量
function box(count) {
(function () {
for (var i = 0; i<count; i++) {}
})();
alert(i); //报错,无法访问
}
//私有化变量和函数
var box = function () {
var age = 100;
function run() {
return '运行中...';
}
return { //直接返回对象
go : function () {
return age + run();
}
};
}();
上面的直接返回对象的例子,也可以这么写:
var box = function () {
var age = 100;
function run() {
return '运行中...';
}
var obj = { //创建字面量对象
go : function () {
return age + run();
}
};
return obj; //返回这个对象
}();
ps: 在全局作用域中使用块级作用域可以减少闭包占用的内存问题,如果没有指向匿名函数的引用。只要函数执行完毕,就可以立即销毁其作用域链了。
浏览器的检测js的优化
- 浏览器的识别
- navigator对象最早由Netscape Navigator2.0引入的navigator对象,现在已经成为识别客户端浏览器的事实标准
检测插件
//列出所有的插件名
for (var i = 0; i < navigator.plugins.length; i ++) {
document.write(navigator.plugins[i].name + '<br />');
}
//检测非IE浏览器插件是否存在
function hasPlugin(name) {
var name = name.toLowerCase();
for (var i = 0; i < navigator.plugins.length; i ++) {
if (navigator.plugins[i].name.toLowerCase().indexOf(name) > -1) {
return true;
}
}
return false;
}
alert(hasPlugin('Flash')); //检测Flash是否存在
alert(hasPlugin('java')) //检测Java是否存在
先写到这里