JavaScript基础回顾笔记
2017年6月23日
script标签属性
script标签的6个属性: async: 表示立刻下载脚本,但不妨碍页面其他操作,如下载其他资源。 charset: 表示通过src制定的代码的字符集,大多数浏览器会忽略该属性。 defer: 表示脚本立即下载,但是延迟到文档完全解析和显示后在执行。 language: 已废弃,表示编写脚本的语言。 src: 外部脚本的地址。 type: 表示编写代码使用的语言内容类型如:text/javascript(默认)
在没有使用defer和async属性时,浏览器会默认按照script标签的的顺序进行执行。
当解释器在对script标签内的脚本进行求值时,当页面在解析外部引入的js代码时(包括下载该js文件),页面中的其余内容都不会被浏览器加载或显示,且页面的处理也会停止。
html5规范规定defer的脚本会优先于dom.ready事件执行,defer的多个脚本按照引入顺序执行,但是实际浏览器中延迟脚本不一定会按照顺序执行,也不一定会在ready事件之前执行。
async会让脚本立即下载,但于defer不同的是,async的脚本并不能保证按照引入的先后顺序执行,并且不能保证会在ready事件之前执行,但是一定会在load事件之前执行!
js类型转换
在js中下面的值会被转换为假: false,null,undefined,空字符串,数字0,非数字NaN 其他任何值都会被转换为真!
js作用域
作用域链:当函数在执行时,在函数内部都形成自己的局部作用域,而函数外部其本身的执行环境又有属于他的作用域,这一层层嵌套的作用域就形成了一个作用域链,当解析器在解析变量时,会现在当前作用域中去找,如果没有就会沿着作用域链一级级的往上找,直到找到为止,或者一直找到到最顶层的全局作用域中也找不到的话,就会产生错误!
垃圾回收:当函数和变量不在需要继续使用时,就会被js的垃圾回收机制回收,并释放其占有的内存!(闭包之所以能将变量保存在内存中,就是因为保持了外部对函数内部函数的引用,而让垃圾回收机制无法回收,从而始终保持在内存中)
常用数组方法
push() //从数组结尾推入元素,返回数组长度,同时会影响原数组
pop() //从数组结尾删除元素,返回移除的元素,同时会影响原数组
shift() //从数组开头删除元素,返回移除的元素,同时会影响原数组
unshift() //从数组开头添加元素,返回数组长度,同时会影响原数组
sort() //排序,通过比较函数返回小于零,等于零,大于零的值来排序,返回排序后的数组,同时会影响原数组
reverse() //反转数组元素顺序,返回反转后的数组,同时会影响原数组
slice() //切割数组元素,第一个参数是开始位置,第二个参数是结束位置(不包含结束位置的元素),返回新数组
splice() //删除,插入,替换数组元素,第一个参数是开始位置,第二个参数是删除的个数,后面的n个参数是要替换或插入的元素,返回删除的元素,同事会影响原数组
every() //所有元素是否符合某个条件,返回true或false,不影响原数组
some() //是否有至少一个元素符合某个条件,返回true或false,不影响原数组
filter() //根据某个条件进行过滤,返回过滤后的数组,不影响原数组
forEach() //遍历数组元素,无返回,不影响原数组
map() //遍历数组元素,返回运行结果组成的新数组,不影响原数组
indexOf() //搜索数组中的元素,返回元素位置
lastIndexOf() //从数组尾部搜索数组中的元素,返回元素位置
reduce() //遍历数组的所有元素,构建一个最终的返回值,参数为函数,函数有四个参数,当前一个元素,当前元素,当前索引,原数组;并且函数返回的任何值都会作为下次运行的第一个参数传给函数
reduceRigth() //从数组的最后一个元素开始遍历数组的所有元素,构建一个最终的返回值,参数为函数,函数有四个参数,当前一个元素,当前元素,当前索引,原数组;并且函数返回的任何值都会作为下次运行的第一个参数传给函数
var values = [1,2,3,4,5];
var sum = values.reduce(function(prev, cur, index, array){
return prev + cur;
});
alert(sum); //15
正则表达式:
g全局搜索,i不区分大小写,m表示多行模式 ( [ { \ ^ $ | ) ? * + .]} 这些字符在正则中都需要转义
设计模式
单例模式:
var Person = function(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
}
//使用静态方法和静态属性
Person.instance = null;
Person.getInstance = function(name) {
if(!this.instance) {
return this.instance = new Person(name);
}
return this.instance;
}
//使用闭包
Person.getInstance = (function(name) {
var instance;
return function() {
if(!instance) {
return instance = new Person(name);
}
return instance;
}
})()
//引入代理
var getInstance = (function(name) {
var instance;
return function() {
if(!instance) {
return instance = new Person(name);
}
return instance;
}
})()
var a = Person.getInstance(’test1’);
var b = Person.getInstance(’test2’);
var Person = (function() {
var instance = null;
var People = function(name) {
if(instance) {
return instance;
}
this.name = name;
instance = this;
}
People.prototype.getName = function() {
return this.name;
}
return People;
})()
工厂模式:
function createPerson(name, age, job) {
var obj = {
name: name,
age: age,
job: job
}
obj.getName = function() {
alert(this.name)
}
return obj;
}
var p1 = createPerson(‘p1’, 21, ‘it’);
var p2 = createPerson(‘p2’, 23, ’net’);
工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。
构造函数模式:
function Person(name, age, jobj) {
this.name = name;
this.age = age;
this.job = job;
this.getName = function() {
alert(this.name);
}
}
var p3 = new Person(‘p3’, 22, ‘work’);
var p4 = new Person(‘p4’, 26, ‘work’);
构造函数模式不仅能够创建多个对象,并且每个对象都可以通过constructor或者通过instanceof来识别对象类型(这里的类型指的是用户定义的类型 - 类,如这里的Person类),但是构造函数模式在创建实例对象时,每个方法都要重新创建一遍。
原型模式:
function Person() {};
Person.prototype = {
constructor: Person,
name: ‘’,
age: 20,
job: ‘it’,
getName: function() {
alert(this.name)
}
};
var p1 = new Person;
var p2 = new Person;
原型模式虽然可以不用每次实例化的时候都创建一遍方法,但是原型模式省略了初始化时传递参数的能力,并且由于原型对象是多个实例共享的,在其属性为非引用类型时,修改属性值可以通过在实例上添加一个同名属性进行覆盖。但是在属性值为引用类型时,只要有一个实例对该属性进行了修改,那么所有实例的这个属性都会改变!
构造函数组合原型模式:
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.friends = [‘xiaoming’, ’toma']
}
Person.prototype = {
constructor: Person,
getFriends: function() {
console.log(this.friends)
}
}
var p1 = new Person(‘p1’, 21, ‘it’); 创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。另外,这种混成模式还支持向构造函数传递参数;可谓是集两种模式之长
动态原型模式:
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.friends = [‘xiaoming’, ’tom’];
if(typeof this.getName != ‘function’) {
Person.prototype.getName = function() {
alert(this.name);
}
}
}
动态原型模式在实例化构造函数是动态的查询原型方法是否存在,如果不存在就动态的进行添加,这样修改会立即反映到所有实例中;不过在这里不能使用对象字面量重写原型,不然会切断现有实例和新原型直接的联系!
寄生构造函数模式:
function Person(name, age, job) {
var o = {
name: name,
age: age,
job: job,
getName: function() {
alert(this.name);
}
}
return o;
}
var p1 = new Person(‘xiaoming’, 22, ‘it’);
寄生构造函数模式除了使用new操作符进行实例化以外,跟工厂模式其实的一模一样的。并且,首先返回的对象跟构造函数或者是构造函数的原型属性之间是没有关系的。不建议使用!
稳妥构造函数模式:
function Person(name) {
var o = {
getName: function() {
alert(name);
},
setName: function(n) {
name = n;
}
}
return o;
}
var p = Person(‘xiaoming’);
稳妥构造函数模式不使用new操作符进行实例化,而是直接调用函数。该模式创建的对象除了使用getName和setName方法以外,没有任何办法访问name的值。
JS继承
原型链:在js里每个构造函数都有一个原型属性,链接到一个原型对象,假如我们让这个构造函数的原型对象等于另一个构造函数的实例,而这个构造函数的原型对象又等于另一个构造函数的实例,那么这一层层的实例与原型的链条就形成了原型链,并且js中所有的对象都是Object的实例,所有原型链的最顶端都会指向Object.prototype。当访问对象的属性时,会先在对象本身去找,如果没有就会沿着原型链一级级的往下去找,直到找到为止!
原型继承:
function Person() {
this.name = ’tom’;
this.friends = [‘xiaoming’, ‘xiaohua’];//该属性为引用属性
}
Person.prototype = {
constructor: Person,
getName: function() {
console.log(this.name)
}
}
function My() {
this.age = 27;
}
My.prototype = new Person();
My.prototype.getAge = function() {
alert(this.age);
}
var my = new My();
原型继承虽然很强大,但是它也存在一些问题。第一,当通过原型继承来继承父类时,父类构造函数中包含的引用类型属性,就会变成子类的原型对象的属性,当包含引用类型的原型属性会被所有实例共享,只要有一个实例修改了该属性,所有实例的该属性都会被改变。第二,在创建子类型的实例时,无法在不影响所有对象实例的情况下,向父类的构造函数传递参数。
借用构造函数继承(经典继承):
function Person(name) {
this.name = name;
this.friends = ['xiaoming’, 'xiaohua']
}
Person.prototype.getName = function() {
alert(this.name);
}
function My(name) {
Person.call(this, name);
}
var my = new My('tom’);
借用构造函数继承可以实现在子类中向父类传递参数,并且可以让子类的每个实例都具有父类构造函数中的引用类型属性的副本,不会在产生所有实例共享该引用类型属性的问题。但是,很明显的借用构造函数只能继承父类构造函数中定义的方法和属性,而父类原型中定义的属性和方法,子类则无法使用。
组合继承(原型继承+借用构造函数继承):
function Person(name) {
this.name = name;
this.friends = ['xiaoming'];
}
Person.prototype = {
constructor: Person,
getName: function() {
alert(this.name);
}
}
function My(name) {
Person.call(this, name);
}
My.prototype = new Person();
var my = new My(‘xiaoyu’);
组合继承也叫伪经典继承,是将原型继承和借用构造函数继承组合到一起使用,这样既通过在原型上定义方法实现了函数复用,又能保证每个实例都有它自己的属性。但是,这种方式会调用两次父类的构造函数,并且在实例对象和原型属性上产生相同的父类属性,如上面代码中的name属性,子类实例对象上有该属性,子类原型上也有一模一样的name属性。
原型式继承:
function object(o) {
function F() {};
F.prototype = o;
return new F;
}
//原型式继承
var person = {
name: ’tom’,
friedns: [‘atom’, ‘rob']
}
//使用object函数
var my = object(person);
//或者使用Object.create()
var I = Object.create(person);
在不需要创建构造函数的时候,想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的!不过包含引用类型的属性始终都是会共享的,这点跟使用原型模式是一样的!
寄生式继承:
function create(o) {
var clone = object(o);
clone.sayHi = function() {
alert(‘Hi’);
}
return clone;
}
var person = {
name: ‘atom’,
age: ‘27'
};
var my = create(person);
my.sayHi();
寄生式继承是于寄生构造函数和工厂模式类似,不过使用寄生式继承为对象添加函数,会因为无法做到函数复用而降低效率,这根构造函数模式类似;
寄生组合式继承:
function setPrototype(per, chi) {
var pro = object(per.prototype); //创建对象
pro.constructor = chi; //增强对象
chi.prototype = pro; //指定对象
}
function Person(name) {
this.name = name;
this.colors = [‘red’, ‘yellow']
}
Person.prototype.getName = function() {
alert(this.name);
}
function My(name) {
Person.call(this, name);
this.age = 22;
}
setPrototype(Person, My);
My.prototype.getAge = function() {
alert(this.age);
}
var my = new My(‘hua’);
寄生式组合继承只调用了一次父类构造函数,并且避免了在子类原型对象上创建不必要的多余的属性。
闭包
闭包:有权访问函数内部变量的函数,称为闭包。 闭包可以将外部函数中变量保存在内存中:因为js的垃圾回收机制是在函数运行完毕后,会销毁函数中的局部活动对象(作为变量对象使用),但是由于函数内部的函数的作用域链还保留着对函数活动对象的引用,导致垃圾回收机制无法将函数局部活动对象销毁,所以函数中的变量就会被保存在内存中,直到闭包函数被销毁后才会从内存中释放!
通常函数的作用域和所有变量都会在函数执行结束后被销毁,但是当函数返回一个闭包时,闭包的作用域链中会包含对外部函数的作用域的引用,所以这个函数的作用域会一直在内存中保存到闭包不存在为止!
由于闭包会携带包含它的函数的作用域,因此会比其他的函数占用更多的内存,过多的时候闭包会导致内存占用过多!
私有变量:
function My() {
var name = ‘xiaoming’;
function age() {
return 27;
}
this.getInfo = function() {
return {
name: name,
age: age()
}
}
}
这里的变量name和函数age只能在函数内部可以访问,这里称为私有变量!而这些能访问私有变量的方法(getInfo)称为特权方法!不过这种方式有个问题,就是只能使用构造函数实现,方法无法复用!
静态私有变量:
var My = (function() {
var name = ‘’;
function age() { return 27; }
function My(val) { name = val; }
My.prototype.getInfo = function() {
return age();
}
My.prototype.setName = function(val) {
name = val;
}
})()
var my = new My();
my.getInfo();
这里构造函数和setName方法都能访问变量name,变量name就变成了一个静态的,由所有实例共享的属性。但是,这里调用setName方法,或者新建一个My的实例,都会赋予name一个新值,并且会影响到所有实例!
模块模式:
var myName = function() {
var name = ’tom’;
function getAge() {
return 27;
}
return {
getName: function() {
return name;
},
setName: function(val) {
name = val;
}
}
}();
单例:只有一个实例的对象!模块模式是为单例创建私有变量和特权方法!
增强的模块模式:
var my = function() {
var name = ’tom’;
var my = new My();
my.getName = function() {
reutrn name;
}
return my;
}()
作用域安全的构造函数:
function Person(name, age, job) {
if(!(this instanceof Person)) { return new Person(name, age, job) }
this.name = name;
this.age = age;
this.job = job;
}
var p1 = Person('p1', 20, 'job1’);
var p2 = new Person('p1', 20, 'job1’);
call/apply
当构造函数在调用时,如果没有使用new操作符,直接调用,那么this将指向window,这样构造函数里添加的属性就会错误的添加到window对象上,所以在构造函数里首先先确认this对象是否为正确的类型实例,如果不是就创建一个新实例返回。但是该模式下,就无法使用call/apply方式的借用构造函数继承!
function Person(name, age, job) {
if(!(this instanceof Person)) { return new Person(name, age, job) }
this.name = name;
this.age = age;
this.job = job;
}
function My(name, age, job) {
Person.call(this, name, age, job)
}
var my = new My(‘aimi’, 20, ‘job’);
my.name //undefined;
这里在My构造函数里使用call方式调用Penson构造函数,由于构造函数My的实例不属于Person构造函数的实例,导致Person.call返回了一个Person类的实例,而无法实现继承!
function Person(name, age, job) {
if(!(this instanceof Person)) { return new Person(name, age, job) }
this.name = name;
this.age = age;
this.job = job;
}
function My(name, age, job) {
Person.call(this, name, age, job)
}
My.prototype = new Person();
var my = new My(‘aimi’, 20, ‘job’);
my.name //aimi;
好在这里只要使用借用构造函数+原型继承的方式就可以实现对Person类的继承了,原因是My通过原型继承继承了Person类,那么My类的实例同时也会是Person类的实例,my instanceof Person === true;就可以完全通过Penson构造函数里的this验证了!
惰性载入函数:
function getName() {
if(isIE) {
getName = function() {
return ’name1’;
}
} else {
getName = function() {
return ’name2’;
}
}
return getName();
}
var getName = function() {
if(isIE) {
return function() {
return ’name1’;
}
} else {
return function() {
return ’name2’;
}
}
}()
惰性加载函数表示函数的if分支只在函数第一次调用的时候执行一次,当确定分支之后,并且确定条件不会改变的情况下,在第一次调用时就直接覆盖了原有函数(示例1),或者根据条件指定函数(示例2),下次在调用的时候就不会在出现分支了!
转载说明
本文允许全文转载,转载请注明来源: 平凡公子 - JavaScript基础回顾笔记