JavaScript知识点扩展
作用域
作用域作用:
1.定义:变量 函数 或者成员在整个程序内可以被访问的范围;
2.也可用于信息隐藏
块作用域:{…}包含的代码就是一个块作用域,JS没有块级作用域;
动态作用域:函数内的成员是否可以被访问只有在运行的时候才能确定
function f(){
alert(x);
}
function f1(){
var x = 1;
f();
}
function f2(){
var x = 2;
f();
}
f1();//报错:x没有定义,说明JS没有动态作用域
f2();
复制代码
JS只有静态作用域lexical,也叫词法作用域或者闭包,在词法创建阶段就确定了相关的作用域。闭包:引用了自由变量的函数。
function f(){
var x = 100;
function g(){
...
}
g();
}
f();
复制代码
function f(){//f()创建的时候(浏览器解析的时候),给f添加一个看不到摸不着的成员[[scope]]
//f.[[scope]] == window
//f()被调用的时候会创建自己的一个词法环境f.le, f.le ->f.[[scope]]
//在预处理阶段(上一行解释,f()刚刚被调用时),f.le{x = undefined, g(){...}},g.[[scope]] == f.le
//在预处理阶段,无论g()是函数声明还是函数表达式创建的都会把g()加入到f.le中
var x = 100;//在执行阶段,f.le{x = 100, g(){...}}
function g(){//在预处理阶段,g.[[scope]] == f.le
...
}
g();//g()在运行时,会创建自己的词法环境,g.le ->g.[[scope]]
//综上形成一条链条
//g.le -> g.[[scope]] == f.le -> f.[[scope]] == window
}
f();
复制代码
JS创建函数的方式
有3种:
1.函数声明
function f() {…}
2.函数表达式
var f = function () {…}
3.Function构造函数
var f = new Function(“参数”,”函数体”)//这种方式创建的作用域永远指向全局,而不是它的父
闭包
,是词法闭包的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而来的实体。
function f1() {
var a = 10;
var b = 20;
function f2() {//f2()函数就是闭包
console.log(a);
}
f2();
}
f1();
复制代码
闭包的好处
1.减少全局变量
//达到每次调用函数add(),a都不断加1
function add(){
var a = 0;
a++;
alert(a);
}
add();
add();
//这样做每次调用a都从0开始
复制代码
//这样可以实现累加效果
var a = 0;
function add() {
a++;
alert(a);
}
add();
add();
//但这样做就增加全局变量
复制代码
//改为闭包的方式
function f(){
var a = 0;
return function(){
a++;
alert(a);
}
}
var result = f();
result();
result();
复制代码
2.减少传递给函数的参数数量
//实现例如基数为2,最大数为3,返回值为2+ 1+2+3的值
//可以这样实现
function add(base,max){…}
//用闭包
function calFactory(base){
return function(max){
var total = 0;
for(var i = 1, i <= max, i++ ){
total +=i;
}
return total + base;
}
}
var adder = calFactory(2);
adder(3);//8//2+1+2+3
adder(4);//12//2+1+2+3+4
复制代码
3.起到封装的作用
(function(){
var m = 0;
function getM(){//闭包
return m;
}
function setM(){//闭包
m = val;
}
window.g = getM;//通向外部的接口
window.s = setM;
})();//立即调用的匿名函数
s(12);
alert(g());
复制代码
闭包的注意事项:
1.对于捕获到的变量只是个饮用,不是复制
function f(){
var num = 1;
function g(){
alert(num);//引用父的num
}
num++;
g();//2
}
复制代码
2.父函数每调用一次,会产生不同的闭包
function f(){
var num = 1;
return function(){
num++;
alert(num);
}
}
var result1 = f();//由函数的预处理可知,每次调用一个函数的时候会创建一个新的词法环境
result1();//2
result1();//3
var result2 = f();//创建一个新的词法环境
result2();//2
result2();//3
复制代码
3.循环中的问题
<body>
<div id = “1”>1</div>
<div id = “2”>2</div>
<div id = “3”>3</div>
for(var i = 1;i <=3;i++){
var ele = document.getElementById(”i”);
ele.onclick = function(){
alert(i);
}
}
</body>
//点击1 2 3 都会弹出4,因为没有块级作用域,所以i相当于全局变量
//解决办法,可以用闭包
<body>
<div id = “1”>1</div>
<div id = “2”>2</div>
<div id = “3”>3</div>
(for(var i = 1;i <=3;i++){
var ele = document.getElementById(”i”);
ele.onclick = (function(id){
return function(){
alert(id);
}
})(i);//每次点击1或2或3都会立即执行函数,从而达到点击几弹出几的效果
//每被调用一次函数,父函数的i都会被捕获,都会重新产生一个闭包
}
</body>
复制代码
伪类
对象的_ _ proto _ _与函数无关,直接指向函数的prototype
//函数有prototype属性,对象有_ _ proto _ _属性,函数也是对象,所以函数也有prototype属性
function Person(){
var age = 30;
this.age = 22;
this.name = “nice”;
}
Person.prototype.headCount = 1;
var p = new Person();
//此时Person()相当于伪类,对象p内部有age和name属性,p也可通过__proto__指向
//Person.protptype,p完全与Person()无关了。
Person.prototype = {xx:”xx”};//将Person的prototype完全改写
console.log(p.xx);//undefined//此时p已经不能取到新的Person.prototype了
console.log(p.headcount);//1//但是原来的p.__proto__指向的Person.prototype还存在着
复制代码
this
this永远指向对象
this可以直接写在全局里面,并且此时永远指向window对象;this也可以写在函数里面,此时氛围三种情况:
第一种情况:
var ele = document.getElementById(”id”);
ele.addElementListener(”click”,function{
console.log(this);//这种情况this永远指向引发事件的对象ele
})
复制代码
第二种情况:
//this的指向是运行时决定,不是编写代码时决定
//运行时函数是谁调用的,this就指向谁
var o = {
name:”nice”;
print:function(){
console.log(this.name);
}
}
o.print();//cj//函数是o调用的
var ff = o.print;
ff();//undefined//此时相当于window.ff(),函数是window调用的
复制代码
第三种情况:
function f(){
this.name = “nice”;
}
复制代码
上面第二种情况改变了this的指向,还可以使用call或apply或bind改变this指向,call和apply和bind都是函数对象都会有的东西
call和apply的区别:
传参的时候call使用参数列表的形式,一个一个地传,而apply使用参数数组的形式
var o = {
name:”nice”;
print:function(){
console.log(this.name);
}
}
var ff = o.print;
ff();//undefined
this.name = “global”;
this.age = 22;
ff();//global
function log(a,b){
console.log(this[a]);
console.log(this[b]);
}
log(”name”,”age”);//global 22
log.call(o,”name”);//nice//改变了this的指向,相当于o.log(“name”)
log.call(o,”name”,”age”);//nice undefined//undefined因为在o对象中没有age变量
log.apply(o,[’’name”,”age”])//nice undefined
复制代码
new自定义的实现
function Person(name, age){
this.name = name;
this.age = age;
}
var p1 = new Person(”cj”, 22);
console.log(p1.name);//“cj”
console.log(p1.age);//22
复制代码
实现编写一个上例中的new
function New(f){//f是一个构造函数
return function(){//return的这个函数相当于上面的new Person()
var o = {”__proto__”:f.prototype};
f.apply(o,arguments);
return o;
}
}
var p2 = New(Person)(”xx”,33);
console.log(p2.name);//“xx”
console.log(p2.age);//33
console.log(p2 instanceof Person);//true
Person.prototype.xx = “aaaa”;
console.log(p2.xx);//“aaaa”
复制代码
封装
function Person (){
var age = 100;//私有成员,外界访问不了
function pm(){//私有成员
console.log(”private method”);
}
this.name = name;//公有成员
this.text = function(){//公有成员
console.log(”public method”);
}
}
复制代码
//上面的代码这样写就没有意义了,应该向下面这样写才有意义
function Person (){
var age = 100;
function pm(){
console.log(this.name);
}
this.name = name;//公有成员
this.text = function(){//公有成员
console.log(”public method”);
pm();
}
}
var p1 = new Person(”cj”);
console.log(p1.text());//public method undefined
//因为pm()前面没前缀,所以调用window中的pm(),为undefined
//将代码pm()变换为pm.call(this)将this的指向为Person
复制代码
//或者用对象工厂的方式解决公有私有的互相调用
function Person(pname){
function pm(){
console.log(self.name);
}
var self = {
name:pname;
test:function(){
pm();
}
};
return self;
}
var p2 = Person(”cj”);
console.log(p2.test());//“cj”
复制代码
继承
浅拷贝
var person = {
name:”cj”;
age:22;
}
var programmer = {
language:”Javascript”;
}
function extend(p, c){
var c = c||{};
for(var prop in p){
c[prop] = p[prop];
}
return c;//没有这个语句也可以,没有的话传回的是个引用
}
extend(person, programmer)
//此时programmer便继承了person的属性
复制代码
var person = {
name:”cj”;
age:22;
address:{
home:”home address”;
office:”office address”;
}
}
var programmer = {
language:”Javascript”;
}
function extend(p, c){
var c = c||{};
for(var prop in p){
c[prop] = p[prop];
}
return c;//没有这个语句也可以,没有的话传回的是个引用
}
extend(person, programmer)
console.log(programmer.name);//“cj”
programmer.name = “nice”;
console.log(person.name);//“cj”//浅拷贝,变量值改变
console.log(programmer.name);//“nice”
programmer.address.home = “Shenzhen”;
console.log(programmer.address.home);//“Shenzhen”
console.log(person.address.home);//“Shenzhen”//子改变了父的值,说明只是浅拷贝
//子和父的引用指向同一个位置
//数组和对象一样,可存在这个问题
//数组也可以用for in循环遍历
复制代码
深拷贝
对象自变量的形式实现的继承
function extendDeeply(p, c){
var c =c||{};
for(var prop in p){
if(typrof p[prop] === “object”){
c[prop] = (p.[prop].constructor === Array)?[]:{};
extendDeeply(p[prop], c[prop]);
}else{
c[prop] = p[prop];
}
}
}
复制代码
用构造函数的形式实现继承
function Parent(){//父对象
this.name = “abc”;
this.address = {home:”home”};
}
function Child(){//子对象
Parent.call(this);//用call形式拷贝
this.language = “js”;
}
c = new Child();
console.log(c.name);//“abc”
P = new Parent();
c.address.home = “Shenzhen”
console.log(p.address.home);//“home”//是深拷贝
复制代码
create实现继承
var p = {name:”cj”};//p.__proto__指向object.prototype,object.prototype.__proto__指向null
function myCreate(p){
var ins;
function F(){};
F.prototype = p;
ins = new F();
return ins;
}
var c = myCreate(p);//系统自带的Object.create(p);
console.log(c.name);//“cj”
复制代码
上面的代码有系统自带的Object.create()
还可以进行属性的增强,例如
var c2 = Object.create(p,{age:{value:20},salary:{value:1000000}});//p是父类继承的,
//后面是子类增强的
console.log(name);//“cj”
console.log(age);//20
复制代码
再谈instanceof
左边是对象,右边是函数
var f = new F();
console.log(f instanceof F);//true//判断时相当于判断是否f.constructor == F,是对的
console.log(f instanceof Object);//true
//很显然像上面一样判断是否f.constructor == Object不成立
//但是上面的那个成立,所以还可以进一步判断
//是否F.prototype.__proto__== Object.prototype,成立
复制代码
类的继承
//创建父类
//创建子类
//建立关系
第一种方式
function P(){}//父类
function C(){}//子类
function P(){}//父类
function C(){}//子类
C.prototype = P.prototype//此时C.prototype.constructor 是P(),所以用类的方法继承后,
//要将原型类型的构造器恢复回来C.prototype = C;
var c1 = new C();
console.log(c1 instanceof C);//true
console.log(c1 instanceof P);//true
C.prototype.xx = “xx”;
var p1 = new P();
console.log(p.xx);//“xx”
//不要用这种方式,因为缺点是子继承父,但是子的东西同样暴露给了父
复制代码
第二种方式
function P(){}//父类
function C(){}//子类
C.prototype = new P();
var c1 = new C();
console.log(c1 instanceof C);//true
console.log(c1 instanceof P);//true
C.prototype.xx = “xx”;
var p1 = new P();
console.log(p.xx);//undefined
//优点:子的东西不会暴露给父
//缺点:在查找时中间多了一层,而且凭空new个P没有什么用,只是起到子与父的桥接
复制代码
第三种形式
function P(){}//父类
function C(){}//子类
function F();
F.prototype = P.prototype;
var f = new F();
C.prototype = f;
//整体代码实际就是上面写过的Object.create(),所以可以直接写成
C.prototype = Object.create(P.prototype);
//优点是:只是中间new了一个空函数,不会有像上面所说的缺点
复制代码
类的继承四部曲:
1创建父类以及添加父类的属性和方法
2创建子类
3立即继承
上面实现了继承,但是应该本能地将子类原型的constructor修正回子类的构造函数
4此时再添加子类的属性,
加入3和4颠倒,继承的时候有等号,相当于完完全全把子类重写成了父类的样子
//举例
function Person(){}//创建父类
Person.prototype.headCount = 1;//添加父类属性
Person.prototype.eat = function(){//添加父类方法
console.log(”eating...”);
}
function Programmer(){}//创建子类
Programmer.prototype = Object.create(Person.prototype);//继承a
Programmer.prototype.constructor = Programmer;//将子类原型的constructor修正回来(本能)a
Programmer.prototype.language = “Javascript”;//添加子类属性
Programmer.prototype.work = function(){//添加子类方法
console.log(”i am writing code in” + this.language);
}
var js = new Programmer();
console.log(js.eat());//eating...
console.log(js.language);//“Javascript”
js.language = “JS”;
console.log(js.language);//“JS”
console.log(js.work());//i am writing code in JS
复制代码
还可以将上面即成的代码(注释里有a)改为下面的代码
createEx(Programmer, Person);
......
function createEx(Child, Parent){
function F(){};
F.prototype = P.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
}
复制代码
function Person(name, age){//创建父类
this.name = name;//父类有实例级别的成员,才写aaaa行代码
this.age = age;
}
Person.prototype.headCount = 1;//添加父类属性
Person.prototype.eat = function(){//添加父类方法
console.log(”eating...”);
}
function Programmer(name, age, title){//创建子类
Person.apply(this, arguments);//把父类实例级别的成员继承了过来aaaa
}
createEx(Programmer, Person);
Programmer.prototype.language = “Javascript”;//添加子类属性
Programmer.prototype.work = function(){//添加子类方法
console.log(”i am writing code in” + this.language);
}
function createEx(Child, Parent){
function F(){};
F.prototype = P.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
}
var person = new Person(“cj”, 20);
console.log(person.name);//”cj”
var p = new Programmer(”nice”, 22, “coder”);
console.log(p.name);//“nice”
person.name = “haha”
console.log(”p.name”);//“nice”//没有影响
复制代码
有时我们会在子里面调用父的东西,用super或base
function Person(name, age){//创建父类
this.name = name;
this.age = age;
}
Person.prototype.headCount = 1;//添加父类属性
Person.prototype.eat = function(){//添加父类方法
console.log(”eating...”);
}
function Programmer(name, age, title){//创建子类
Person.apply(this, arguments);//把父类实例级别的成员继承了过来
}
createEx(Programmer, Person);
Programmer.prototype.language = “Javascript”;//添加子类属性
Programmer.prototype.work = function(){//添加子类方法
console.log(”i am writing code in” + this.language);
Programmer.base.eat();//****新加的语句,使得子调用了父的方法
}
function createEx(Child, Parent){
function F(){};
F.prototype = P.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.super = Child.base = Parent.prototype;//****新加的语句
}
var csharp = new(”cj”, 22, “teacher”);
console.log(csharp.name);//“cj”
console.log(csharp.work());//i am writing code in Javascript
//eating...
复制代码
多态
拥有
function F(){};
var f = new F();
f.name = “cj”;
console.log(f.hasOwnProperty(”name”));//true//是否有
F.prototype.age = 22;
console.log(f.hasOwnProperty(”age”));//false//f对象和原型对象是两个对象,不存在拥有
console.log(Object.hasOwnProperty(f, ”name”));//false
//不要这么用,因为是对象级别的hasOwnPorperty
console.log(F.prototype.isPrototypeOf(f));//true//是否是...的原型
console.log(Object.getPrototypeOf(f));//F{}//找f的原型
复制代码
方法重载
编译时的多态,典型的代表是方法重载(大概意思就是调用一个参数时,就是一个参数的//A方法,调用两个参数时就是两个参数的A方法),编译时就确定了
JS中不允许同名的方法,后者会覆盖前者
arguments,实参的个数,在函数里才会有这个对象,是类似数组的对象
fucntion demo(a, b){
console.log(demo.length);//形参的个数
console.log(arguments.length);//实参的个数
}
复制代码
可变长度的重载
function add(){
var total = 0;
for(var i = arguments.length - 1; i >= 0; i—){
total += arguments[i];
}
return total;
}
console.log(add(1));//1
console.log(add(1, 2));//3
复制代码
参数的个数不一样
function fontSize(){
var ele = document.getElementById(”js”);
if(arguments.length == 0){
return ele.style.fontSize;
}else{
ele.style.fontSize = arguments[0];
}
}
fontSize(18);
console.log(fontSize());//18
复制代码
参数类型不一样
function setting(){
var ele = document.getElementById(”js”);
if(typeof arguments[0] === “object”){
for(p in arguments[0]){
ele.style[p] = arguments[0][p];
}else{
ele.style.fontSize = arguments[0];
ele.style.backgroundColor = arguments[1];
}
}
setting(18, “red”);
//setting({fontSize:28, backgroundColor:”green”});
复制代码
方法重写
运行时多态:重写
下面的例子算一种
function demo(o){
o.run();
}
var o = {run:function(){
console.log(”o is running...”);
}};
demo(o);//o is running...
var p = {run:function(){
console.log(”p is running...”);
}};
demo(p);//p is running...
//依据原型链的特点,在实例上重写一个与构造函数一样的方法,实例使用自己的方法
复制代码
var f = new F();
F.prototype.run = function(){
coslole.log(”FFF”);
}
f.run();//FFF
f.run = function(){//重写
console.log(”fff”);
}
f.run();//fff
//重写的过程中还可以调用父的东西
f.run = function(){
console.log(”ffff”);
F.prototype.run();
}
f.run();//ffff
//FFF
复制代码
其他情况
function Parent(){
this.run = function(){
console.log(”parent is running”);
}
}
function Child(){
Parent.call(this);//继承父类
var parentRun = this.run;//保留父的方法的引用,看看以后是否需要调用父的方法
this.run = function(){//子类重写run方法
console.log(”child is running”);
parentRun();//调用父刚刚记录的东西
}
}
var c = new Child();
c.run();//child is running
//parent is running
复制代码
其他情况
function Parent(){}
Parent.prototype.run = function(){
console.log(”parent is running”);
}
Child.prototype = Object.create(Parent.prototype);//继承
Child.super = Parent.prototype;//把父的东西弄过来
function Child(){}
Child.prototype.run = function(){
console.log(”child is running”);
Child.super.run();//调用父的东西
}
var c = new Child();
c.run();//child is running
//parent is running
复制代码