这篇是2015年初学JavaScript教程时所学内容, 作为笔记在此记录,当时写的比较匆忙可能会有错。
小部分有问题的地方后来有更正过.
JavaScript在浏览器环境中
全局对象
- 浏览器中的全局对象是window。
- 所有在浏览器中声明的全局对象都隶属于window这个对象。
document
- window.document表示浏览器窗口中的HTML页面。
- document.write()将内容写入页面。
- 页面中的元素就是document里的成员,来试试将document中的所有东西都写出来看看:
for(x in document)
document.write(x+"<br/>"");
HTML中的JavaScript
- 在
<script></script>
标记中。 - 在
<script>
的src属性或archive指定的外部文件中。 - 在某个HTML标记的事件处理器中。
外部JavaScript文件
<script src="util.js"></script>
- 一个纯粹的代码文件,没有HTML标记。
事件处理器
<p onMouseOver="alert('hi');">
表示鼠标经过后作出的动作。
双引号中如果要出现引号就要使用单引号。
`onMouseOut="alert(‘bye’);"表示鼠标不在这个标签所作出的动作。
还有很多以后有时间添加。
body事件
- onLoad是页面在装载显示之前做的事情。
- onUnload是页面在关掉之前做的事情。
这两句必须用在body标签中。
简单对话框
- alert()
- confirm()会提示yes或no,并随着不同的选择返回true/false。
- prompt()可以输入
浏览器下方的状态栏
- status=
当此行生效时会使状态栏显示出相应文字。 - defaultStatus=
定时器
- setInterval()函数。
该函数用于设置时间间隔(interval=间隔),接受两个参数。第一个参数是要执行的动作,第二个参数是时间间隔(以毫秒为单位)。
<script>
count = 10;
function update(){
if( count >= 0)
status = count--;
}
</script>
<body onLoad='setInterval(update() ,1000)' >
</body>
这样有某些浏览器可以看出来左下角会从10开始倒计时。
变量作用域
下面的例子都用这段代码来演示:
var x = 10;
function foo(){
alert(x);
}
function bar(){
var x=20;
foo();
}
bar();
静态作用域
- 又称词法作用域,在编译阶段就可以决定变量的引用。
- 由程序定义的位置决定。
这里在两个函数外定义了x,它的作用域位于全局,可以用于foo函数和bar函数。foo的外层作用域就是全局作用域,构成了一种嵌套关系,内部没有定义x。bar函数当中定义了x。
foo函数要调用x时沿着这条作用域的链去找x,所以使用的是全局环境下的x。
动态作用域
与程序运行时刻决定,一般使用动态栈来管理,Js并不使用动态作用域。
如果使用动态作用域的思路来考虑:在bar函数中调用的x栈顺序如图
按照程序向下执行的顺序依次在栈中创建了如图的元素,最后调用foo函数来获取x的值时函数会取栈最后添加的x:20。
Js变量作用举
- js使用静态作用域
- js没有块级作用域 只有函数中的作用域
- ES5中使用词法环境管理静态作用域
词法环境
- 组成
- 环境记录(形参、变量、函数等)
- 对外部词法环境的引用(outer)
- 一段代码开始执行前,先初始化词法环境
- 形参
- 函数定义
- 变量定义
词法环境的构造过程
- 全局环境初始化
表达式与运算符
关系运算符==
==
运算符在当等号左右的数据类型不同时返回Number(a)===Number(b)
的值。
Number(\n)的值为零,因此’ “\n\n\n” == false '的值为true
==
运算符也有例外,null/undefined进行==运算时不进行隐式类型转换:
0 == null; //false
null == false; //false
"undefined" == undefined; //false
逻辑运算!
!!表示取x表达式运行结果的Boll值。
逻辑运算 && 和 ||
&&
和||
运算符执行后表达式的返回值不是布尔值而是具体得数的结果。
语句
for/in语句
功能:遍历对象属性。举例:
function Car (id, type, color){
this.type = type;
this.color = color;
this.id = id;
}
var benz = new Car("benz", "black", 12345);
for(var key in benz) {
if(benz.hasOwnProperty(key)){
console.log(key + ":" + benz[key]);
}
alert(key + ":" + benz[key]);
}
这里不能用.
运算符,必须用[ ]
。而且遍历出的属性可能有方法,所以可以在for/in语句中使用if(this.hasOwnProperty(key))
来判断一个属性是不是一个方法。
闭包
函数执行作用域的变量被临时保留.
var a = 1;
var func = (function(){
var a = 2;
return function() {
a++;
alert(a);
}
})();
func();//3
func();//4
Js面向对象
创建对象
直接创建对象字面量var circle = {x:0, y:0, radius: 2};
对象中的属性可以随时添加。只要var *** = new Object(){};
之后像为元素赋值那样写就会在对象中添加属性。
删除对象属性使用delete
关键字
遍历对象中所有属性
使用for (var x in o)
,O是一个对象,x会记录下每次for遍历对象属性的名字。在调用时使用以下方式。
var o = new Object();
o.name = "John Mike";
o.age = 30;
o.salary = 300;
for(var x in o){
alert(o[x]);
}
只会遍历可枚举类型的属性,且顺序不固定。
函数的构造方式调用
function Rect(w,h){
this.width = w;
this.height= h;
this.area = function(){
return this.width * this.height;};
}
var r = new Rect(5,10);
alert(r.area());
函数new调用时会新建一个空对象, 其__proto__被绑定到对应function上, this会绑定在此空对象上, function如果return的typeof为"function"或"object", 则返回return后的变量, 否则返回上面new运算符调用创建的这个对象.
prototype
在声明对象构造器的时候可以用.prototype来创建对象构造器,这部分类似于使用了static,是不同对象默认共享的。但是也有不同之处,不同的static是统一一致的,而不同的.prototype方法是独立的,只是在一开始被赋了统一的初值。但是这样可能会在对数组操作时出现一些问题。
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = {"Shelby","Court"};
}
Person.prototype = {
constructor: Person,
sayName: function(){
alert(this.name);
}
};
var person1 = new Person("Nicholas",29,"Software Engineer");
var person2 = new Person("Greg",29,"Doctor");
alert(person1.friends === person2.friends);
alert(person1.friends === person2.sayName);
向对象原型中添加属性时可以分别用单个语句Person.prototype.*** = ***
这样的结构来表示。若要批量添加则要使用
Person.prototype = {
constructor: Person,
***: ***,
***: *** ;}
constructor
对象类型构造器。
创建对象:
//new operator
var o = new Object();
var a = new Array();
var d = new Date();
//object literal
var o = {x: 1};
var a = [1, 2, 3];
对象一用new关键字来创建,或者用创建列表。
new关键字后面的称为构造器,即constructor。
自定义constructor
Js中使用函数来自定义构造器,在函数中使用this.来创建属性(可以是函数)。之后使用new关键字来创建对象。
常见的自定义构造器有三种形式。第一种称为函数的定义,function Employee(){}
,第二种是函数的表达式:var Employee = function() {}
,第三种是使用new关键字var Employee = new Function()
注意大写。
不是所有的函数都可以当成constructor,比如var o = new Math.min();
就不能当做构造器使用,一般来说用户自定义的构造器是可以使用new关键字的。
this
函数中的this指向函数的调用者。全局中的this指向全局对象。
new Function中的this指向全局对象(window)。
eval中的this指向调用上下文中的this,指向父函数或父对象。
场景 | 值 |
---|---|
全局环境 | 全局对象(window) |
constructor | 新创建的对象 |
函数的`.`调用 | 函数的调用者 |
new Function | 全局对象(window) |
eval | 调用上下文中的this |
apply、call
可以使用apply或者call方法来使某些对象调用其他对象的方法, 改变调用时的this。:
function Point(x, y){
this.x = x;
this.y = y;
this.move = function(x, y){
this.x += x;
this.y += y;
}
}
var point = new Point(0, 0);
point.move(1, 1);
var circle = {x:0, y:1, r:1};
//apply方法
point.move.apply(circle, [1,1]);
//call方法
point.move.call(circle, 1, 1);
apply和call方法略有区别,call第二个参数后是用逗号隔开的参数列表;apply接受的是数组。
原型继承
prototype属性使得创建出的对象都有一些共同的属性和方法,这些属性方法属于原型而不属于对象。语法如下:
function Teacher(){
this.courses = [];
}
Teacher.prototype = {
job: 'teacher',
setName: function(name){
this.name = name;
},
addCourse: function(course){
this.courses.push(course);
}
}
原型链
Object.prototype是原型链的顶层,它的__proto__指向null。
属性查找
对象调用属性的时候会从本身开始一次向原型链上查找是否有个这属性。
属性修改
修改对象的属性时修改对象自身的属性,不管来自于对象本身还是来自于原型,如果对象本身没有这个属性,则添加这个属性到对象本身。
属性删除
使用delete删除对象中的属性只会删除对象本身拥有的属性,不会删除原型上的属性。
判断属性是否源于对象本身hasOwnProperty
对象名.hasOwnProperty(‘属性名’)
ES5中的原型继承
Object.create(proto[,propertiesObject])可以直接创建一个对象并制定原型。接受的参数是要定义的原型方法。
var teacher = {
job:'teacher',
courses: [],
setName: function(name){
this.name = name;
},
addCourse: function(course){
this.courses.push(course);
}
}
var bill = Object.create(teacher);
这样就可以不适用new构造器,直接用原型对象在prototype原型链下创建出来一个对象。
全局变量
在函数外用var定义的变量,使用window.定义的变量,以及在函数中没有使用var而直接创建出的变量都是全局变量。后两种会将变量定义到window的属性中,可以用delete删除。但是第一种定义方式无法使用delete删除。
封装
之前的方法,使用prototype来隐藏:
function A(){
this.a = null;
}
var pro = A.prototype;
pro.step1 = function(){....}
pro.api = function(){....}
这种方法也有问题,公有私有不分。
更适用的封装形式:
function A(){
var _config = ['A', 'B', 'C'];
this.getConfig = function(){
return _config;
}
}
var pro = A.prototype;
pro._step1 = function (){....}
pro._step2 = function (){....}
pro.api = function() {....}
但是如果想达到区分出protected和public还需要自己动手做一些标识符来实现,比如上列代码中如果方法前加了下划线就是protected的,没有下划线的就是public的。
继承
类继承
如果将类B的prototype设置为new另外类A的构造器,再将类B的.prototype.constructor属性设置为类B就可以实现类的继承。如果不设置类B的constructor属性,它就会默认指向类A的constructor,这样在new类B的时候总是创建类A的对象,无法达成继承的效果。
function ClassA(){}
function ClassA(){} //类A的构造器
ClassA.classMethod = function(){} //类A的方法1
ClassA.prototype.api = function(){}
function ClassB(){
ClassA.apply(this, arguments);
}//类B的构造器
ClassB.prototype = new ClassA();
ClassB.prototype.constructor = Class B;
ClassB.prototype.api = function(){
ClassA.prototype.api.apply(this, arguments);
}//类B调用类A的api方法
var b = new ClassB();//实现B继承自A
如图为继承时的原型链:
原型继承
原型继承是Js中自带的继承方式。直接用Object.create基于原型直接创建出一个新的对象。比如代码中的obj对象就以proto对象为原型创建出的,obj对象有个proto指针指向proto对象。
function(){
var proto = {
action1: function(){...},
action2: function(){...}
}
var obj = Object.create(proto);
}
这个Object.create只在ES5后的高版本浏览器中才有,对于低版本浏览器可以做出如下的兼容:
var clone = (function(){
var F = function() {};
return function(proto){
F.prototype = proto;
return new F();
}
})();
JavaScript中的对象类型
原生对象
JavaScript原生对象类型一共分为以下几种
Undefined
Null
Object
Bool
String
Number
Functon
Array
Date
RegExp
Error
Mate
JSON
全局对象
arguments
再细分可分为:标准类型、标准内置对象、构造器、对象。
标准类型
标准类型包括:
Undefined Null Object Bool String Number
标准内置对象
标准内置对象包括:
Object Bool String Number Functon Array Date RegExp Error Math JSON 全局对象
构造器
构造器包括:
Object Bool String Number Function Array Date RegExp Error
其他对象
对象包括:
Math JSON 全局对象 Arguments
类型识别的方法
于课件中提到的类型识别一共有四种方法。
type of
type of 可识别除Null之外的标准类型
Undefined Null Object Bool String Number
instance of
instance of可识别内置对象类型
Object Bool String Number Functon Array Date RegExp Error Math JSON 全局对象
但是不能识别原始类型。
constructor
constructor可识别标准类型,不能识别Undefined Null
类型,可以识别自定义对象类型。
Object.protope.toString
Object.protope.toString可以识别标准、内置对象类型。不能识别自定义的object。
具体在代码中的类型识别实现
从上述的描述可以看出,四种方法中三种都有严重的局限性,可以使用的范围最为广泛的就是Object.protope.toString方法,除了自己定义的对象,对于Js的原生对象均可以良好的识别。这里列出可以常用的类型识别函数,这个函数可以将接受的数据以完全小写的字符串输出该数据的类型。
function type(object)
{
return Object.prototype.toString.call(object).slice(8,-1).toLowerCase();
}
其中,toString函数中并没有.call方法,而且以一个整数为例,如果使用
type(2){
return Object.protope.toString.call(object)
}
输出的形式为[Object Number],这种形式对于我们判断类型时对比字符串是比较麻烦的,因而在函数之后添加.slice(8,-1)
使输出字符串从第8个字符开始,输出结束从倒数第一个字符开始。
再添加.toLowerCase()
使输出的字符串转化成小写。这样进行===
的对比时右侧比较项的书写就更统一了。