1. 浏览器
1.1 语言类型
-
编译性语言 c c++
- 优点: 快, 写完后才整体翻译成一个文件
- 缺点: 不跨平台
-
解释性语言 javascript php
-
优点: 跨平台,不需要编译成文件,写一行编译一行;单线程
-
缺点: 稍慢
-
.java — javac — 编译 — .class — jvm虚拟机 — 解释执行
java不属于两种语言中的任一种,是 oak 语言
1.2 主流浏览器及内核
IE trident
chrome webkit/blink
firefox gecko
opera presto
safari webkit
1.3 值类型 — 数据类型
- 不可改变的原始值(栈数据 先进后出)
- number、string、boolean、undefined、null
- 引用值(堆数据)
- array、object、function、…date、RegExp
var arr = [1, 2];
var arr1 = arr;
arr = [1, 3]; // arr重新指向一个新的堆地址,但是arr1的地址仍是原来的
document.write(arr1); // [1, 2]
document.write(arr); // [1, 3]
var a = 10;
var b = ++a - 1 + a++; // 11 - 1 + 11 a后加变成12
// b = 21 a = 12
var a = 1;
var b = a-- + --a; // 0
// 先算--a,此时a=0,然后a-- + 0 = 0,之后a = -1
var b = --a + --a; // -1
// b = 0 + -1
// a = 1-1 = 0,然后--a ===> a = -1
1 / 0 = Infinity(是一个number)
0 / 0 = NaN(Not a Number)
转化为boolean为false的值:undefined、null、NaN、""(空字符串)、0、false
var a = 1 && 2 + 2; // 4 第一个表达式为true,返回第二个表达式的值
var a = 0 && 2 + 2; // 0 第一个表达式为false,返回第一个表达式的值
作业:
n可输入 var n = parseInt(window.prompt('input'));
1、计算2的n次幂,n可输入,n为自然数
2、n的阶乘,n可输入
3、斐波那契数列 1 1 2 3 5 8输出第n项
4、输入一个三位数的正整数,反向输出。eg:输入456,输出654
5、输入a,b,c三个数字,打印出最大的
6、打印出1000以内的质数
1.4 条件语句
switch case; break; continue跳出本次循环,执行下次循环
switch(条件) {
case 判断:
......;
break; // 判断成立后跳出循环 下面的不执行,必须写在循环里面
case 判断:
......
}
2. 对象
2.1 对象的创建方法
-
1、字面量 var obj = {} 对象字面量/对象直接量
-
2、构造函数
-
系统自带的构造函数 new Object()
-
自定义
-
-
构造函数内部原理
-
在函数体最前面隐式的加上this = {}
-
执行 this.xxx = xxx;
-
隐式的返回this
-
function Person(name, height) {
// var this = {}
this.name = name;
this.height = height;
this.say = function() {
console.log(this.say);
}
// return {}; // 显式更改的,对象值、数组、function会被执行,person会返回Object{}。
// return 123; // 显式更改的,改成原始值会被忽略,仍会执行return this; new了就不可能返回原始值
// return this; // 隐式执行的
}
var person = new Person('a', 1);
var person1 = new Person('b', 2);
-
包装类new Number()、new String()、new Boolean()
所有原始值都没有属性与方法,但在添加时并不会报错,因为包装类的存在
var num = 4; num.len = 3; // 隐式执行new Number(4).len = 3; 但创建后就删除了delete // 到console.log时:执行new Number(4).len console.log(num.len); // undefined var str = "abcd"; str.length = 2; // new String('abcd').length = 2; delete 被截断的字符串'ab'已被销毁,跟之前定义的无关 console.log(str); // abcd console.log(str.length); // 4 这个length是对象字符串本身就有的属性new String('abcd').length
试题:
var str = 'abc';
str += 1; // abc1
var test = typeof(str); // string
if(test.length == 6) { // test.length = 6
test.sign = "typeof的返回结果可能是String";// 原始值赋属性值要调用包装类 new String(test).sign = 'xxx'
}
// new String(test).sign
console.log(test.sign);// undefined
function employee(name, code) {
this.name = 'wangli';
this.code = "A001";
}
var newEmp = new employee("zhangming", "A002");// 虽然传值了,但是在函数里面并没有用到,this.name是写死的
document.write( newEmp.name ); // wangli
document.write( newEmp.code ); // A001
function Person(name, age, sex) {
var a = 0;
this.name = name;
this.age = age;
this.sex = sex;
function sss() {
a++;
document.write(a);
}
this.say = sss;
}
var oPerson = new Person();
oPerson.say(); // 1
oPerson.say(); // 2
var oPerson1 = new Person();
oPerson1.say(); // 1
var x = 1, y = z = 0;
function add(n) {
return n = n + 1;
}
y = add(x);
function add(n) {
return n = n + 3;
}
z = add(x);
// 执行完毕 x = 1 y = 4 z = 4
// console.log结果是[1,2,3,4,5]的是
// A
function foo(x) {
console.log(arguments)
return x
}
foo(1,2,3,4,5)
// B 错
function foo(x) {
console.log(arguments)
return x
}(1,2,3,4,5) // 什么也不输出 是函数声明不是表达式
// C
(function foo(x) {
console.log(arguments)
return x
})(1,2,3,4,5) // 实参列表
// D
function foo() {
bar.apply(null, arguments)
}
function bar(x) {
console.log(arguments)
}
foo(1,2,3,4,5)
parseInt(3, 8) // 把3当成8进制 3
parseInt(3, 2) // NaN
parseInt(3, 0) // 3或NaN,不同浏览器的结果
function b(x, y ,a) {
arguments[2] = 10;
alert(a);
}
b(1,2,3); // 10
// 改变函数体为:
a = 10; alert(arguments[2]); // 10
// 求一个字符串的字节长度
// stringObject.charCodeAt(index)返回指定位置字符的unicode编码,返回值0-65535;<=255英文(一个字节),>255中文(两个字节)
function retByteslen(target) {
var count = 0;
for(var i = 0; i < target.length; i++) {
if(target.charCodeAt(i) <= 255) {
count++;
} else if(target.charCodeAt(i) > 255) {
count += 2;
}
}
return count;
}
// 全都按英文计算,遇到中文时字节数加1
function retByteslen(target) {
var count, len;
count = len = target.length;
for(var i = 0; i < len; i++) { // 每次循环都要计算target.length,可以先提取出来简便计算
if(target.charCodeAt(i) > 255) {
count++;
}
}
return count;
}
3 函数
3.1 函数定义(函数声明、函数表达式)
// 函数声明
function theFirstName() {} // 小驼峰命名 theFirstName.name = theFirstName 默认名字是自己
// 1. 命名函数表达式
var test = function abc() {
document.write("a");
}
// console打印时,test是函数体,abc会显示没有定义 但是test.name = abc
// 2. 匿名函数表达式 --- 函数表达式
var demo = function () {
document.write('b');
}
// demo.name = demo 默认名字是自己
3.2 组成形式
函数名称
参数:形参 实参
返回值return, 也是结束条件(没有返回值就直接return;表示结束 return 123; 返回值为123,同时结束)
所有的实参都存在实参列表arguments中,所有的形参都存在函数名中
// 实参较多
function sum(a) {
console.log(arguments); // [1,2,3,4]
}
sum(1,2,3,4)
// 形参较多,arguments中仅有实参中的数,当形参a变化时,arguments[0]会随a的变化而变化,但是arguments[1]为undefined,不会随b的变化而改变
function sum(a, b, c, d) {
console.log(sum); // ƒ sum(a, b, c, d) {console.log(sum);}
console.log(sum.length); // 4
}
sum(1)
作业:
1. 写一个函数,告知你所选定的小动物的叫声
2. 写一个函数,实现加法计数器
3. 定义一组函数,输入数字,逆转并输出汉字形式
4. n的阶乘
5. 斐波那契数列
6.输入一串低于10位的数字,输出这串数字的中文大写
eg:input:10000 output:壹万
input:1001010 output:壹佰万壹仟零壹拾
作用域:变量(变量作用域又称上下文)和函数生效(能被访问)的区域
js
js执行步骤:语法分析、预编译、解释执行
函数声明整体提升 变量声明仅声明提升
预编译前奏
1、imply global暗示全局变量:即任何变量,如果变量未经声明就赋值,此变量就为全局对象所有
eg: a=123; eg: var a=b=123;
2、一切声明的全局变量,全是window的属性
eg: var a=123; ===> window.a = 123;
预编译四部曲
1、创建AO对象(Activation Object)(执行期上下文)
2、找形参和变量声明,将变量和形参名作为AO属性名,值为undefined
3、将实参值和形参统一
4、在函数体里面找函数声明,值赋予函数体
// 1
function fn(a) {
console.log(a); // 1xx function a() {}
var a = 123;
console.log(a); // 123
function a() {}
console.log(a); // 123
var b = function () {}
console.log(b);//function () {}
function d() {}
}
fn(1);
AO {
a: undefined ---> 1形参 ---> function a() {}函数体, --->123赋值
b: undefined, ---> function () {}
d: function d() {}
}
// 2
function test(a, b) {
console.log(a); // 1
c = 0;
var c;
a = 3;
b = 2;
console.log(b); // 2
function b() {}
function d() {}
console.log(b); // 2
}
test(1);
AO {
a: undefined, ---> 1形参 ---> 3赋值
b: undefined, ---> function b() {}函数体 ---> 2赋值
c: undefined, ---> 0赋值
d: function d() {}
}
// 3
function test(a, b) {
console.log(a); // function a() {}
console.log(b); // undefined
var b = 234;
console.log(b); // 234
a = 123;
console.log(a); // 123
function a() {}
var a;
b = 234;
var b = function () {}
console.log(a); // 123
console.log(b); // function () {}
}
test(1);
AO {
a: undefined 形参 ---> 1 ---> function a() {}函数体 ---> 123赋值
b: undefined 形参 ---> 234赋值 ---> 234赋值 ---> function () {}赋值
}
console.log(test);// function test(test) {...}
function test(test) {
console.log(test); // function test() {}
var test = 234;
console.log(test); // 234
function test() {}
}
test(1);
var test = 123;
// 全局GO 局部AO AO没有的去GO找
GO {
test: undefined ---> function test(test) {...}---> 123
}
AO {
test: undefined 形参 ---> 1 ---> function test() {}函数体 ---> 234赋值
}
//GO {
// fn: function fn() {...}
// global: undefined ---> 100
//}
global = 100;
function fn() {
console.log(global); // undefined
global = 200;
console.log(global); // 200
var global = 300;
}
fn();
//AO {
// global: undefined ---> 200 ---> 300
//}
var global;
// GO {
// test: function test(){...}
// a: undefined ---> 10
// c: 234
//}
function test() {
console.log(b); // b is not definedxxx undefined
if(a) {
var b = 100;
}
c = 234;
console.log(c); // 234
}
var a;
test();
// AO {
// b: undefined 与if执行与否无关
//}
a = 10;
console.log(c);// 234
function bar() {
return foo;
foo = 10;
function foo() {}
var foo = 11;
}
console.log(bar()); // function foo() {}
//AO {
// foo: undefined ---> function foo() {}提升了 然后下面的不会执行
//}
//GO {
// bar: function bar() {...}
//}
console.log(bar()); // 11
//AO {
// foo: undefined ---> function foo() {} ---> 10 ---> 11
//}
function bar() {
foo = 10;
function foo() {}
var foo = 11;
return foo;
}
//GO {
// demo: function demo(e) {...}
// a: 100
// f: 123
//}
a = 100;
function demo(e) {
function e() {}
arguments[0] = 2;
document.write(e);// 2
if(a) {
var b = 123;
function c() {}// 现在if里面不允许加function
}
var c;
a = 10;
var a;
document.write(b); // undefined
f = 123;
document.write(c);//function c() {} 现实应该是undefined
document.write(a);//10
}
var a;
demo(1);
//AO {
// e: undefined 形参 ---> 1 ---> function e() {} ---> 2
// b: undefined
// c: undefined 形参 ---> function c() {}
// a: undefined ---> 10
//}
document.write(a);// 100
document.write(f);// 123
var str = false + 1;
document.wrtie(str);// 1
var demo = false == 1;
document.write(demo); // false
// "undefined" && "NaN" 都是字符串转为布尔值为true 可以进入循环 (-1 + NaN + 0 = NaN)
if(typeof(a) && -true + (+undefined) + "") {
document.write('qqqqqq'); // qqqqqq
}
if(11 + "11"*2 == 33) {
document.write('qqqqqq'); // qqqqqq
}
// true + false - false = 1 || document.write('dddddd')后面的不执行不打印
!!" " + !!"" - !!false||document.write('dddddd')
(window.foo || (window.foo = 'bar'));
// ()优先级大于 || 所以(window.foo = 'bar')先执行 最后window.foo = 'bar'而不是"undefined"
ctrl +’’ 去标签
ctrl + f 查找 find all 全部选择要查找的项 然后可以全部改变
4 作用域
[[scope]]: 每个javascript函数都是一个对象,对象中有些属性可以访问,有些不可以,不可以访问的这些属性仅供javascript引擎存取,[[scope]]就是其中一个。[[scope]]就是作用域,其中存储了运行期上下文的集合
作用域链: [[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式链接,我们把这种链接叫做作用域链。
运行期上下文: 当函数执行时,会创建一个称为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,函数每次
执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,它所产生的执行期上下
文被销毁。
查找变量: 从作用域链的顶端依次向下查找。
function a() {
function b() {
var b = 234;
a = 10;
}
var a = 123;
b();
console.log(a); // 10 b函数执行完后,访问a,拿的就是a的AO(引用),在b执行后,a的值已经被改变成了10
}
var glob = 100;
a();
// a定义 a.[[scope]] ---> 0: GO {function a(){...},glod:100}
// a执行 a.[[scope]] ---> 0: AO {function b(){...},a:123}
// 1: GO {function a(){...},glod:100}
// 因为b定义的时候在a里面,所以b最先的作用域就是a的AO
// b定义 b.[[scope]] ---> 0: AO {function b(){...},a:123} a的AO
// 1: GO {function a(){...},glod:100}
// b执行 b.[[scope]] ---> 0: AO {b:234} b的AO
// 1: AO {function b(){...},a:123}
// 2: GO {function a(){...},glod:100}
function a() {
function b() {
var bbb = 234;
document.write(aaa); //123
}
var aaa = 123;
return b; // 闭包
}
var glob = 100;
var demo = a();// demo就相当于b,把b给重新保存起来了,当a断了之后也不受影响
demo();
// a定义 a.[[scope]] ---> 0: GO {function a(){...},glod:100,demo:a()}
// a执行 a.[[scope]] ---> 0: AO {function b(){...},aaa:123}
// 1: GO {function a(){...},glod:100,demo:a()}
// demo定义 demo.[[scope]] ---> 0: AO {demo: function b(){...}}
// 1: GO {function a(){...},glod:100,demo:a()}
// demo执行 demo.[[scope]] ---> 0: {bbb: 234}
// 1: {demo: function b(){...}}
// 2: GO {function a(){...},glod:100,demo:a()}
function a() {
var num = 100;
function b() {
num++;
console.log(num);
}
return b;
}
var demo = a();
demo();
demo();
// a定义 a.[[scope]] ---> 0: GO {function a(){...},demo:a()}
// a执行 a.[[scope]] ---> 0: aAO {num: 100,function b(){...}}
// 1: GO {function a(){...},demo:a()}
// demo定义 demo.[[scope]] ---> 0: aAO {num: 100,function b(){...}}
// 1: GO {function a(){...},demo:a()}
// demo执行 demo.[[scope]] ---> 0: demoAO {}
// 1: aAO {num: 100-->101,function b(){...}}
// 2: GO {function a(){...},demo:a()}
// demo二次执行 demo.[[scope]] ---> 0: demoAO {}
// 1: aAO {num: 101-->102,function b(){...}}
// 2: GO {function a(){...},demo:a()}
5 闭包
当内部函数被保存到外部时,将会生成闭包。闭包会导致原有作用域链不释放,造成内存泄漏。
闭包会导致多个执行函数共用一个公有变量,如果不是特殊需要,应尽量防止出现这种情况
闭包的作用:
1、实现公有变量 eg:函数累加器
// 累加器
function add() {
var count = 0;
function demo() {
count++;
console.log(count);
}
return demo;
}
var counter = add();
counter(); // 1
counter(); // 2
counter(); // 3
2、可以做缓存(存储结构) eg:eater
function test() {
var num = 100;
function a() {
num++;
console.log(num); // 101
}
function b() {
num--;
console.log(num); // 100
}
return [a,b];
}
var myArr = test();
myArr[0]();
myArr[1]();
// test定义 test.[[scope]] ---> 0: GO {a: function(){...}, myArr: test()}
// test执行 test.[[scope]] ---> 0: testAO {num: 100, a: function, b: function}
// 1: GO {a: function(){...}, myArr: test()}
// myArr[0]定义 myArr[0].[[scope]] ---> 0: testAO {num: 100, a: function, b: function}
// 1: GO {a: function(){...}, myArr: test()}
// myArr[0]执行 myArr[0].[[scope]] ---> 0: myArr[0]AO {}
// 1: testAO {num: 100--> 101, a: function, b: function}
// 2: GO {a: function(){...}, myArr: test()}
// myArr[1]定义 myArr[1].[[scope]] ---> 0: testAO {num: 101, a: function, b: function}
// 1: GO {a: function(){...}, myArr: test()}
// myArr[1]执行 myArr[1].[[scope]] ---> 0: myArr[1]AO {}
// 1: testAO {num: 101--> 100, a: function, b: function}
// 2: GO {a: function(){...}, myArr: test()}
function eater() {
var food = ""; // 隐式的存储结构
var obj = {
eat: function () {
console.log("i am eating " + food);
food = "";
},
push: function (myFood) {
food = myFood;
}
}
return obj;
}
var eater1 = eater();
eater1.push('banana');
eater1.eat(); // i am eating banana
3、可以实现封装。属性私有化 eg:Person()
function Deng(name, wife) {
var prepareWife = "xiaozhang";
this.name = name;
this.wife = wife;
this.divorce = function () {
this.wife = prepareWife;
}
this.changePrepareWife = function (target) {
prepareWife = target;
}
this.sayPrepareWife = function () {
console.log(prepareWife);
}
}
var deng = new Deng('deng', 'xiaoliu');
deng // Deng {name: "deng", wife: "xiaoliu", divorce: ƒ, changePrepareWife: ƒ, sayPrepareWife: ƒ}
deng.divorce();
deng.wife // "xiaozhang" this.wife能够变化是因为闭包,divorce执行后能够用到Deng的AO
// 但是deng.preparWife并没有,相当于preparWife是其私有化的属性
4、模块化开发,防止污染全局变量(命名空间)
var name = "123";
var init = (function () {
var name = 'abc';
function callName() {
console.log(name);
}
return function () {
callName();
}
}());
init(); // abc
var init2 = (function () {
var name = 'wer';
function callName() {
console.log(name);
}
return function () {
callName();
}
}());
init2(); // wer
// 不会影响全局中相同名字的变量,各个局部的变量之间也不受影响
测试:
function test() {
var arr = [];
for(var i = 0; i < 10; i++) { // 改为let i = 0就可以打印出0 1 2 3 4 5 6 7 8 9
arr[i] = function () {
console.log(i); // 打印出来十个 10,因为在循环结束后i=10,所有的myArr[j]连的都是testAO
}
}
return arr;
}
var myArr = test();
for(var j = 0; j < 10; j++) {
myArr[j]();
}
function test() {
var arr = [];
for(var i = 0; i < 10; i++) {
// 用立即执行函数打印出来0 1 2 3 4 5 6 7 8 9
(function (j) {
arr[j] = function () {
console.log(j);
}
}(i));
}
return arr;
}
var myArr = test();
for(var j = 0; j < 10; j++) {
myArr[j]();
}
题:
// <ul>
// <li>a</li>
// <li>a</li>
// <li>a</li>
// <li>a</li>
// </ul>
// 使用原生js,addEventListener,给每个li元素绑定一个click事件,输出他们的顺序
function test () {
var liCollection = document.getElementsByTagName('li');
for(var i = 0; i < liCollection.length; i++) {
(function (j) { // 不用立即执行函数会输出4个4,不会输出他们的索引值
liCollection[j].onclick = function () { // liCollection[j].addEventListener('click', function())
console.log(j);
}
}(i))
}
}
test();
// , 把最后一个值赋给变量 var a = (1-1, 1+1); // 2
var f = (
function f() {
return "1";
},
function g() {
return 2;
}
)();
typeof f; // "number"
var x = 1;
if(function f() {}) { // ()把函数f转换成表达式,最后返回的不是false,之后函数f也销毁
x += typeof f; // "1undefined"
}
x;
6 立即执行函数
定义:此类函数没有声明,在一次执行过后即释放。适合做初始化工作
形式:( function (形参) {…} (实参) ); 或者 ( function (形参) {…} ) (实参); 括号里面是匿名函数,不用起名,可以有返回值
var num = (function (a, b, c) {
var d = a + b + c * 2 - 2;
return d;
}(1, 2, 3));
console.log(num); // 7
只有表达式才能被执行符号执行,能被执行符号()执行的表达式,这个函数的名字会被自动忽略,也就相当于是立即执行函数,执行完就没有了
function test() {
var a = 123;
}(); // 这样函数不能被执行,因为是函数声明不是表达式,只有test();才能执行
var test = function () {
console.log('a');
}() // 函数表达式可以被执行
+ function test() {
console.log('a');
}() // 前面的符号是+ - !可以被执行 之后再打印test就显示 test is not defined; * / 不可以
function test(a, b, c, d) {
console.log(a + b + c + d);
}(1, 2, 3, 4) // 不报错但是也不执行
console.log(test); // function test() {...}
// 相当于
function test(a, b, c, d) {
console.log(a + b + c + d);
}
(1, 2, 3, 4);
7 原型
1、定义
原型是function对象的一个属性,定义了构造函数制造出的对象的公共祖先。通过该构造函数产生的对象,可以继承该原型的属性和方法。原型也是对象。
// Person.prototype -- 原型
Person.prototype.LastName = "Zhang"; // 原型属性后代会继承
Person.prototype.say = function () {
console.log('hehe');
}
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
var person = new Person('haha', 18, 'male'); // 以自己有的属性为主,自己没有就去原型找
person.LastName // Zhang
2、利用原型特点和概念,可以提取共有属性
Car.prototype.height = 1400; // 共有属性
Car.prototype.lang = 4900; // 共有属性
Car.prototype.carName = "BMW"; // 共有属性
// 简便
Car.prototype = {
height: 1400,
lang: 4900,
carName: "BMW"
}
function Car(color, owner) {
this.color = color;
this.owner = owner;
}
var car = new Car('red', 'zhang');
var car = new Car('green', 'li');
原型的"增删改查"必须作用到prototype上:delete car.height
后显示true,但是``car.height`仍然是1400,并没有影响原型,只影响自己
delete Car.prototype.height
可以删除 Car.prototype.height = 1300;
可以修改
3、对象如何查看原型 —> 隐式属性 __proto__
Person.prototype.name = 'abc';
function Person() {
// var this = {
// __proto__: Person.prototype
// }
}
var obj = {
name: "sunny"
}
var person = new Person();
person.__proto__ = obj;
person.name // "sunny"
Person.prototype.name = 'sunny';
function Person() {}
Person.prototype.name = 'cherry'; // cherry
var person = new Person();
Person.prototype.name = 'cherry'; // cherry
Person.prototype = { // sunny
name: 'cherry'
}
person.name // Person.prototype.name不论更改的位置在哪,结果都会改变(在原有的基础上改变属性值)
// Person.prototype = {...}只有放在var person = new Person();前面,更改的内容才会变化 (把原型给改变了)
// var obj = {name: "a"};
// var obj1 = obj;
// obj = {name: "b"};
// obj1.name // "a"
// obj.name // "b"
Person.prototype.name = 'sunny';
function Person() {
// var this = {
// __proto__: Person.prototype
// }
}
var person = new Person();
Person.prototype = { // Person.prototype换了个新空间,但是__proto__指向的还是原来的空间
name: 'cherry'
}
person.name // sunny
// Person.prototype = {name: "a"};
// __proto__ = Person.prototype;
// Person.prototype = {name: "b"};
4、对象如何查看对象的构造函数 —> constructor(可以手动更改)
function Car() {}
var car = new Car();
car.constructor // ƒ Car() {}
Car.prototype // constructor: ƒ Car()
// __proto__: Object
// 更改 constructor
function Person() {}
Car.prototype = {
constructor: Person
}
function Car() {}
var car = new Car();
// 如果更改发生在var car = new Car();之后,car.constructor还是 ƒ Car() {}
car.constructor // ƒ Person() {}
Car.prototype // constructor: ƒ Person()
// __proto__: Object
8 原型链
1、如何构成原型链
// Grand.prototype.__proto__ = Object.prototype 所有对象的最终原型
Grand.prototype.lastName = "Deng";
function Grand() {}
var grand = new Grand();
Father.prototype = grand;
function Father() {
this.name = "xuming";
this.fortune = {
card1: "visa"
};
this.num = 100;
}
var father = new Father();
Son.prototype = father;
function Son() {
this.hobbit = "smoke"
}
var son = new Son();
son.fortune.card2 = "master"; // 不是覆盖更改,是调用更改,引用值自己修改。但是原始值只能覆盖
father.fortune // {card1: "visa", card2: "master"}
son.num++;
father.num // 100
son.num // 101 只更改自己的num值
// a.sayName() sayName里面的this指向: 谁调用的这个方法,this就指向谁
Person.prototype = {
name: "a",
sayName: function () {
console.log(this.name);
}
}
function Person () {
this.name = "b";
}
var person = new Person();
person.sayName() // b
Person.prototype.sayName() // a
2、原型链上属性的增删改查
Person.prototype = {
height: 100
}
function Person () {
this.eat = function () {
this.height++;
}
}
var person = new Person();
person.eat()
person.height // 101
person.__proto__.height // 100
3、绝大多数对象最终都会继承自Object.prototype
Person.prototype = {} ---> Object.prototype原型链终端
function Person() {}
例外:
var obj = Object.create(null); // 没有属性
obj // {}
// No properties
// 可以加属性
obj.name = "123";
obj // name: "123"
// 加个原型
obj.__proto__ = {name: "sunny"};
obj // {name: "123", __proto__: {…}}
// name: "123"
// __proto__:
// name: "sunny"
// __proto__: Object
// 但是并不被系统承认,obj.name访问不到
var obj = Object.create(null);
obj.__proto__ = {name: "sunny"}; // __proto__原型是隐式的内部属性
obj.name // undefined
undefined null没有包装类,也没有原型,就是个原始值,没有toString方法
4、Object.create(原型prototype, 特性definedProperty);
var obj = {name: "sunny", age:13};
var obj1 = Object.create(obj); // obj1的原型就是obj
obj1 // {}
// __proto__:
// age: 13
// name: "sunny"
// __proto__: Object
Person.prototype.name = "sunny";
function Person() {}
var person = Object.create(Person.prototype);
person // Person {}
// __proto__:
// name: "sunny"
// constructor: ƒ Person()
// arguments: null
// caller: null
// length: 0
// name: "Person"
// prototype: {name: "sunny", constructor: ƒ}
// __proto__: ƒ ()
// [[FunctionLocation]]: VM903:2
// [[Scopes]]: Scopes[1]
// __proto__: Object
一旦经历了var的操作,所得出的属性即window上的属性,这种属性叫做不可配置属性
不可配置的属性 delete不掉
var num = 123;
delete window.num // false
9 继承
发展过程
1、传统形式 —> 原型链 (grand, father, son)
过多的继承了没用的属性
2、借用构造函数(Person, Student call apply)
不能继承借用构造函数的原型
每次构造函数都要多走一个函数
3、共享原型
不能随便改变自己的原型,会连着把father的原型也给改了
Father.prototype.lastName = "Deng";
function Father() {}
function Son() {}
Son.prototype = Father.prototype;
var son = new Son();
son.lastName // "Deng"
封装成函数
Father.prototype.lastName = "Deng";
function Father() {}
function Son() {}
function inherit(Target, Origin) {
Target.prototype = Origin.prototype;
}
inherit(Son, Father); // 要写在var son = new Son(); 先继承再使用
var son = new Son();
son.lastName // "Deng"
4、圣杯模式 = 共享原型 + 原型链
Father.prototype.lastName = "Deng";
function Father() {}
function Son() {}
function inherit(Target, Origin) {
function F() {}
F.prototype = Origin.prototype; // 必须在Target.prototype = new F();前面,不然无用
Target.prototype = new F();
Target.prototype.constuctor = Target; // 让Target的构造函数仍然是自己,要不会继承自father的
Target.prototype.uber = Origin.prototype; // 保存Target最终的原型方便后期使用
}
inherit(Son, Father); // 要写在var son = new Son(); 先继承再使用
var son = new Son();
son.lastName // "Deng"
闭包的第三个作用: 3、可以实现封装。属性私有化 eg:Person()
function Deng(name, wife) {
var prepareWife = "xiaozhang";
this.name = name;
this.wife = wife;
this.divorce = function () {
this.wife = prepareWife;
}
this.changePrepareWife = function (target) {
prepareWife = target;
}
this.sayPrepareWife = function () {
console.log(prepareWife);
}
}
var deng = new Deng('deng', 'xiaoliu');
deng // Deng {name: "deng", wife: "xiaoliu", divorce: ƒ, changePrepareWife: ƒ, sayPrepareWife: ƒ}
deng.divorce();
deng.wife // "xiaozhang" this.wife能够变化是因为闭包,divorce执行后能够用到Deng的AO
// 但是deng.preparWife并没有,相当于preparWife是其私有化的属性
利用此特性进一步封装inherit
var inherit = (function () {
var F = function () {}; // 私有化变量
return function (Target, Origin) {
F.prototype = Origin.prototype;
Target.prototype = new F();
Target.prototype.constuctor = Target;
Target.prototype.uber = Origin.prototype;
}
})
10 命名空间
管理变量,防止污染全局,适用于模块化开发
var name = "123";
var init = (function () {
var name = 'abc';
function callName() {
console.log(name);
}
return function () {
callName();
}
}());
init(); // abc
var init2 = (function () {
var name = 'wer';
function callName() {
console.log(name);
}
return function () {
callName();
}
}());
init2(); // wer
// 不会影响全局中相同名字的变量,各个局部的变量之间也不受影响
模仿jquery的连续调用方法
var deng = {
smoke: function () {
console.log('smoking...');
return this;
},
drink: function () {
console.log('drinking...');
return this;
},
perm: function () {
console.log('perming...');
return this;
}
}
deng.smoke().drink().perm(); // smoking... drinking... perming...
属性表示方法
obj.prop
隐式转化为obj["prop"]
obj["prop"]
属性拼接
var deng = {
name1: {name: "zhangsan"},
name2: {name: "lisi"},
name3: {name: "wanger"},
name4: {name: "mawu"},
sayName: function (num) {
return this['name' + num];
}
}
deng.sayName(1); // {name: "zhangsan"}
deng.sayName(2); // {name: "lisi"}
对象枚举(es6-es11)
var x = 1, y = z = 0;
function add(n) {
return n = n + 1;
}
y = add(x); // y = 4 x = 1
function add(n) {
return n = n + 3;
}
z = add(x); // z = 4
原始值相比较时比较的是值,引用值比较的是地址
123 == 123 // true 但是 {} == {} // false
11 this
1、函数预编译过程 this —> window
function test(c) {
// var this = Object.create(test.prototype); // new test();之后才改变this的,否则一直是window
var a = 123;
function b() {}
}
// AO {
// arguments: [1],
// this: window,
// c: 1,
// a: undefined,
// b: function () {}
// }
test(1);
new test();
2、全局作用域里 this —> window
3、call apply 可以改变函数运行时this指向
4、obj.func(); func()里面的this 指向 obj即谁调用这个函数this就指向谁,没人调用指向window
var obj = {
a: function () {
console.log(this.name); // obj调用的该方法,因此输出abc
},
name: 'abc'
}
obj.a();
题:
var name = "222";
var a = {
name: "111",
say: function () {
console.log(this.name);
}
}
var fun = a.say; // function () {console.log(this.name)}
fun() // 222 全局执行函数 没有调用
a.say() // 111 a调用
var b = {
name: "333",
say: function (fun) {
// this ---> b
fun();//但是fun 谁也没调用 走预编译
}
}
b.say(a.say); // 222
b.say = a.say; // function () {console.log(this.name)}
b.say(); // 333
12 arguments
arguments.callee 即这个函数的引用 立即执行函数可能用到
function test() {
console.log(arguments.callee); // ƒ test() {console.log(arguments.callee);} 就是test函数
console.log(test.caller); // null
}
test();
// 求100的阶乘 需要用到前面函数执行的结果 但是立即执行函数执行过后并不能找到
var num = (function (n) {
if(n == 1) {
return 1;
}
return n * arguments.callee(n - 1);
}(100))
func.caller 在哪个环境里面被调用的·
function test () {
demo();
}
function demo() {
console.log(demo.caller); // ƒ test () {demo();}
}
test();
在ES5环境中 “use strict”; 会报错 不能使用callee和caller
题:
// 1
var foo = '123';
function print() {
var foo = '456';
this.foo = "789"; // this指向window
console.log(foo); // 456
}
console.log(foo); // 789
print();
// 2
var foo = '123';
function print() {
this.foo = "789";
console.log(foo); // 789 自己没有上window找foo
}
print();
// 3
var foo = '123';
function print() {
// var this = Object.create(print.prototype)
this.foo = "789"; // 这个时候的this不指向window
console.log(foo); // 123
}
new print();
// 1. 运行test() 和 new test() 的结果分别是什么
var a = 5;
function test() {
a = 0;
alert(a); // test(): 0 new test():0
alert(this.a); // test(): 5 new test():0 X undefined
var a;
alert(a); // test(): 0 new test():0
}
function print() {
console.log(foo); // 2x undefined
var foo = 2;
console.log(foo); // 2
console.log(hello); // hello is not defined
}
print();
function print() {
var test;
test();
function test() {
console.log(1);
}
}
// AO {
// test: undefined ---> test()
// }
print(); // 1
function print() {
var x = 1;
if(x == "1") console.log("One!");
if(x === "1") console.log("Two!");
}
print(); // One!
function print() {
var marty = {
name: "marty",
printName: function() {console.log(this.name);}
}
var test1 = { name: "test1" };
var test2 = { name: "test2" };
var test3 = { name: "test3" };
test3.printName = marty.printName;
var printName2 = marty.printName.bind({ name: 123 });
marty.printName.call(test1); // test1
marty.printName.apply(test2); // test2
marty.printName(); // marty
printName2(); // 123
test3.printName(); // test3
}
print();
var bar = { a: "002" }; // {a: 'a'}在打印的时候值已经更改了
function print() {
bar.a = 'a';
Object.prototype.b = 'b';
return function inner() {
console.log(bar.a); // 002 X a
console.log(bar.b); // b
}
}
print()();
作业: 浅层 深层克隆
// 浅层克隆 原始值obj与obj1的改变互不影响 引用值obj与obj1的改变相互影响
var obj = {
name: 'abc',
age: 123,
sex: 'female',
card: ["visa", "unionpay"]
}
var obj1 = {}
function clone(origin, target) {
var target = target || {}; // 如果没有传target就新建一个空对象
for(var prop in origin) {
target[prop] = origin[prop];
}
return target;
}
clone(obj, obj1);
obj1 // {name: "abc", age: 123, sex: "female", card: Array(2)}
// 原始值obj与obj1的改变互不影响
obj.last="haha";
obj1 // {name: "abc", age: 123, sex: "female", card: Array(2)}
obj // {name: "abc", age: 123, sex: "female", card: Array(2), last: "haha"}
// 引用值obj与obj1的改变相互影响
obj.card.push("master")
obj1 // {name: "abc", age: 123, sex: "female", card: Array(3)}
obj // {name: "abc", age: 123, sex: "female", card: Array(3), last: "haha"}
// 深层克隆 互不影响
var obj = {
name: 'abc',
age: 123,
card: ["visa", "unionpay"],
wife: {
name: "bcd",
son: {
name: "aaa"
}
}
}
var obj1 = {};
// 遍历对象 for(var prop in obj)
// 1. 判断对象是不是原始值 typeof() object
// 2. 判断是数组还是对象 instanceof toString constructor
// 3. 建立相应的数组或对象
// 4. 递归
function deepClone(origin, target) {
var target = target || {},
toStr = Object.prototype.toString,
arrStr = "[object Array]";
for(var prop in origin) {
if(origin.hasOwnProperty(prop)) { // 不复制原型链上的
if(origin[prop] !== "null" && typeof(origin[prop]) == 'object') {
// if(toStr.call(origin[prop]) == arrStr) {
// target[prop] = [];
// } else {
// target[prop] = {};
// }
// 用三目运算符简化
target[prop] = toStr.call(origin[prop]) == arrStr ? [] : {};
deepClone(origin[prop], target[prop]); // 递归
} else {
target[prop] = origin[prop];
}
}
}
return target;
}
deepClone(obj, obj1);
obj // {name: "abc", age: 123, card: Array(2), wife: {…}}
// age: 123
// card: (2) ["visa", "unionpay"]
// name: "abc"
// wife:
// name: "bcd"
// son: {name: "aaa"}
obj1 // {name: "abc", age: 123, card: Array(2), wife: {…}}
// age: 123
// card: (2) ["visa", "unionpay"]
// name: "abc"
// wife:
// name: "bcd"
// son: {name: "aaa"}
// obj变化 obj1不变
obj.card.push("master");
obj // {name: "abc", age: 123, card: Array(3), wife: {…}}
obj1 // {name: "abc", age: 123, card: Array(2), wife: {…}}
13 数组
数组
定义:
-
new Array(length/content); 只传一个参数时默认是数组的长度length,且不能是小数
-
字面量 var arr = [];
数组的读和写:
-
arr[num] // 不可以溢出读 结果是undefined
-
arr[num] = xxx; // 可以溢出写
数组常用方法:
-
改变原数组: push pop shift unshift sort reverse splice
-
不改变原数组: concat join split toString slice
仿写push方法:
// 每次都往原数组的最后一位加push的内容,返回push后的数组长度
var arr = [1, 2, 3];
Array.prototype.push = function () {
for(var i = 0; i < arguments.length; i++) {
this[this.length] = arguments[i];
}
return this.length;
}
pop: 剪切数组的最后一位,返回该值
shift:从数组的最前面减
unshift:往数组的最前面加
reverse: 把原数组逆转顺序
splice:arr.splice(从第几位开始,截取长度,在切口处添加新的数据) 返回截取的值
-1位表示最后一位,splice在定义第几位时,
splice = function (pos) {
pos += pos > 0 ? 0 : this.length;
}
var arr = [2, 3, 5, 7];
arr.splice(-1, 0, 9); // -1 + 4 = 3,从第三位开始加
arr // [2, 3, 5, 9, 7]
sort:对数组排序,按ASCII码排
var arr = [1, 3, 6, 10, 34, 89, 9];
arr.sort(); // [1, 10, 3, 34, 6, 89, 9]
// sort可以接收函数,按照自己的方式排序
// 要求: 1. 必须写俩形参 2. 排序结果看返回值
// 2.1 当返回值为正数时,后面的数在前 升序
// 2.2 当返回值为负数时,前面的数在前 升序
// 2.3 当返回值为0,不动
arr.sort(function(a, b) {
return a - b; // [1, 3, 6, 9, 10, 34, 89] 升序
return b - a; // [89, 34, 10, 9, 6, 3, 1] 降序
});
// 给一个随机数组乱序
var arr = [1, 2, 4, 8, 9];
// Math.random(); // 产生(0, 1)之间的随机数
arr.sort(function () {
return Math.random() - 0.5; // 正负值不定
});
var cheng = {
name: "cheng",
age: 18,
sex: "male",
face: "handsome"
}
var deng = {
name: "deng",
age: 38,
sex: "male",
face: "haha"
}
var zhang = {
name: "zhang",
age: 27
}
// 按年龄排序
var arr = [cheng, deng, zhang];
arr.sort(function (a, b) {
return a.age - b.age; // 0: {name: "cheng", age: 18, sex: "male", face: "handsome"}
// 1: {name: "zhang", age: 27}
// 2: {name: "deng", age: 38, sex: "male", face: "haha"}
});
// 按字节长度排序
function retBytes(str) {
var num = str.length;
for(var i = 0; i < str.length; i++) {
if(str.charCodeAt(i) > 255) {
num++;
}
}
return num;
}
var arr = ["habg", "哈", "hagyq哈2276287哈yf", "as哈cu", "hjahgsty6wqppqjhhg ffjhdkldlkd"];
arr.sort(function(a, b) {
return retBytes(a) - retBytes(b);
// ["哈", "habg", "as哈cu", "hagyq哈2276287哈yf", "hjahgsty6wqppqjhhg ffjhdkldlkd"]
});
不改变原数组:
var arr1 = [2, 3, 5, 7];
var arr2 = [2, 6, 9, 7];
arr1.concat(arr2); // 把arr2拼到arr1的后面 // [2, 3, 5, 7, 2, 6, 9, 7]
arr1 // [2, 3, 5, 7]
arr2 // [2, 6, 9, 7]
// arr.slice(从第几位开始截取, 截取到哪位不包括该位) 返回截取的数据 不会改变原数组 如果需要该数据需要另赋值
var arr = [1, 2, 5, 8];
var newArr = arr.slice(1, 3); // [2, 5]
var newArr = arr.slice(1); // [2, 5, 8] 只填一个参数,从该位开始截到最后
var newArr = arr.slice(); // [1, 2, 5, 8] 不填参数 全部截取
// arr.join() 传字符串形式的 把数组每一位用所传的字符串连接 返回字符串
var str = arr.join() // "1,2,5,8"
var str = arr.join("") // "1258"
var str = arr.join("-") // "1-2-5-8"
// str.split() 按所传字符串拆分,拆分字符串为数组
str.split("-") // ["1", "2", "5", "8"]
str.split("") // ["1", "-", "2", "-", "5", "-", "8"]
str.split(" ") // ["1-2-5-8"]
str.split() // ["1-2-5-8"]
// 字符串连接 栈内存连接 先进后出 数组 堆 散列结构
// 字符串拼接
var str = "alibaba";
var str1 = "baidu";
var str2 = "tencent";
var str3 = "toutiao";
var str4 = "wangyi";
var str5 = "haha";
var str6 = "kajajssis";
var arr = [str, str1, str2, str3, str4, str5, str6];
arr.join(""); // "alibababaidutencenttoutiaowangyihahakajajssis"
arr.join("-"); // "alibaba-baidu-tencent-toutiao-wangyi-haha-kajajssis"
arr.join() // "alibaba,baidu,tencent,toutiao,wangyi,haha,kajajssis"
类数组arguments
1、可以利用 属性名 模拟数组的特性
2、可以动态增长length属性
3、如果强行让类数组调用push方法,会根据length属性值的位置进行属性的扩充
// 属性要为索引(数字)属性,必须有length属性,最好加上push
var obj = {
"0": "a",
"1": "b",
"2": "c",
"length": 3,
"push": Array.prototype.push,
"splice": Array.prototype.splice // 加上obj的样式变成数组
}
obj.push('d');
obj // {0: "a", 1: "b", 2: "c", 3: "d", length: 4, push: ƒ}
// 要完全变成数组的样子,要加splice方法
obj // ["a", "b", "c", "d", push: ƒ, splice: ƒ]
题:
var obj = {
"2": "a",
"3": "b",
"length": 2,
"push": Array.prototype.push
}
obj.push('c');
obj.push('d');
obj // {2: "c", 3: "d", length: 4, push: ƒ}
Array.prototype.push = function (target) {
obj[obj.length] = target; // obj[2] = c obj[3] = d
obj.length++; // length: 3 length: 4
}
// 封装type 可以返回具体的类型
// [] -- array , {} -- object , function -- function , new Number() -- [object Number] , 123 -- number
// 不完善
function setType(target) {
var toStr = Object.prototype.toString,
arrStr = "[object Array]",
numStr = "[object Number]";
if(toStr.call(target) == arrStr) {
return 'array';
} else if(toStr.call(target) == numStr) {
return numStr;
} else if(target === null) {
return "null";
} else {
return typeof(target);
}
}
//
function type(target) {
var ret = typeof(target);
var template = {
"[object Array]": "array",
"[object Object]": "object",
"[object Number]": "number-object", // 包装类
"[object Boolean]": "boolean-object",
"[object String]": "string-object"
}
if(target === null) {
return "null";
} else if(ret == "object") {
var str = Object.prototype.toString.call(target);
return template[str];
} else {
return ret;
}
}
// 数组去重 要求在原型链上编程
Array.prototype.unique = function () {
var newArr = [],
temp = {},
len = this.length;
for(var i = 0; i < len; i++) {
if(!temp[this[i]]) {
temp[this[i]] = "a"; // 随便赋的值,主要看属性名
newArr.push(this[i]);
}
}
return newArr;
}
var arr = [1,"h",2,2,"ha","a",3,4,3,2,455,12,2,12,"ha"];
arr.unique(); // [1, "h", 2, "ha", "a", 3, 4, 455, 12]
题:
function Person(name, age, sex) {
var a = 0;
this.name = name;
this.age = age;
this.sex = sex;
function sss() {
a++;
document.write(a);
}
this.say = sss;
}
var oPerson = new Person();
oPerson.say(); // 1
oPerson.say(); // 2
var oPerson1 = new Person();
oPerson1.say(); // 3xx 1
(function (x) {
delete x; // 形参相当于var 不能delete
return x;
})(1); // 1
function test() {
console.log(typeof(arguments)); // object
}
test();
var h = function a() {
return 23;
}
// a写在表达式里面 最后就没有了
console.log(typeof a()); // Uncaught ReferenceError: a is not defined
作业:
1、一个字符串[a-z]组成,请找出该字符串第一个只出现一次的字母
function firstAlphabet(str) {
for(var i = 0; i < str.length; i++) {
}
}
2、字符串去重
14 try…catch
try {
// 在try里面发生的错误,不会执行错误后的try里面的代码,接着执行catch里面的命令与try外面的命令 不会让后续代码终止
}catch(e) {
// 如果try里面发生错误,会执行catch里面的命令
// 自动保存error,有两个值error.message error.name。可自己处理错误信息
} finally {}
Error.name的六种值对应的信息:
1、EvalError: eval()的使用与定义不一致
2、RangeError:数值越界
3、ReferenceError:非法或不能识别的引用数值
4、SyntaxError:发生语法解析错误
5、TypeError:操作数类型错误
6、URIError:URI处理函数使用不当
15 es5严格模式
“use strict”;
- 不再兼容es3的一些不规则语法。使用全新的es5规范
- 两种用法
- 全局严格模式
- 局部函数内严格模式(推荐)
- 即一行字符串,不会对不兼容严格模式的浏览器产生影响
- 不支持with, arguments.callee, func.caller,变量赋值前必须声明,局部this必须被赋值(Person.call(null/undefined)赋值什么就是什么),拒绝重复属性和参数
var obj = {
name: "obj"
}
var name = "window";
function test() {
var name = "scope";
with(obj) { // with可以改变作用域链,with作用域链的最顶端是obj对象,obj对象充当里面代码的AO
console.log(name); // obj
}
}
test();
// 非严格模式的this指向window
"use strict";
function test() {
console.log(this);
}
test(); // undefined 不再指向window
new test(); // new的话会显示一个新对象 打印出: test {} 名字加对象的标准形式
test.call({}); // Object {}
test.call(123); // 严格模式显示123 非严格模式会显示成包装类
// 非严格模式相当于 new Number(123)
//Number {123}
//__proto__: Number
//[[PrimitiveValue]]: 123
// eval把字符串当做代码执行 es3.0不能使用
"use strict";
var a = 123;
eval('console.log(a)'); // 123
// eval可以改变作用域
16 dom
1、DOM — Document Object Model 文档对象模型
2、DOM定义了表示和修改文档所需的方法。DOM对象即为宿主对象,由浏览器厂商定义,用来操作html和xml功能的一类对象的集合。或者称DOM是html和xml的标准编程接口
3、基本操作
查
查看元素节点
-
document 整个文档
-
document.getElementById() 元素id在ie8以下的浏览器中,不区分id大小写,也返回匹配name属性的元素
-
.getElementsByTagName() 标签名
-
.getElementsByName() 只有部分标签name可生效(表单,表单元素,img,iframe)
-
.getElementsByClassName() 类名 ie8及以下的ie版本中没有,可以多个class一起
-
.querySelector() css选择器 ie7及以下版本没有 不实时(相当于取的是原来的副本,对其操作不会影响原来的)
-
.querySelectorAll() css选择器 ie7及以下版本没有 不实时
<div></div>
<script type="text/javascript">
var div = document.getElementsByTagName('div')[0];
div.style.width = "100px";
div.style.height = "100px";
div.style.backgroundColor = "red";
// 点击事件 点击div后变绿,再点击变红
var count = 0;
div.onclick = function() {
count++;
if(count % 2 == 1) {
div.style.backgroundColor = "green";
}else {
div.style.backgroundColor = "red";
}
}
</script>
// 选项卡功能 点击显示不同的块
<div class="wrapper">
<button class="active">111</button>
<button>222</button>
<button>333</button>
<div class="content" style="display: block;">111111111</div>
<div class="content">222222222</div>
<div class="content">333333333</div>
</div>
<script type="text/javascript">
var btn = document.getElementsByTagName('button');
var div = document.getElementsByClassName('content');
for(var i = 0; i < btn.length; i++) {
(function(n) {
btn[n].onclick = function() {
for(var j = 0; j < btn.length; j++) {
btn[j].className = "";
div[j].style.display = "none";
}
this.className = "active";
div[n].style.display = "block"; // 不是立即执行的 i可能与当前btn的不对应 所以用立即执行函数
}
}(i))
}
</script>
<style>
.content {
width: 100px;
height: 100px;
border: 1px solid red;
display: none;
}
.active {
background-color: green;
}
</style>
// 新建div 用定时器让它动起来
<script>
var div = document.createElement('div'); // 新建个div,但此时在body里面看不到
document.body.appendChild(div); // 把div推到body里面
div.style.width = "100px";
div.style.height = "100px";
div.style.backgroundColor = "red";
div.style.position = "absolute";
div.style.left = "0";
div.style.top = "0";
// 动画
//var speed = 1;
//var timer = setInterval(function() {
// speed += speed/20;
// div.style.left = parseInt(div.style.left) + speed + "px";
// div.style.top = parseInt(div.style.top) + speed + "px";
// // 动画停止
// if(parseInt(div.style.top) > 200 && parseInt(div.style.left) > 200) {
// clearInterval(timer);
// }
//}, 100);
// 绑定小事件
document.onkeydown = function(e) {
switch(e.which) {
case 38: // 键盘上键
div.style.top = parseInt(div.style.top) - 5 + "px";
break;
case 40: // 键盘下键
div.style.top = parseInt(div.style.top) + 5 + "px";
break;
case 37: // 键盘左键
div.style.left = parseInt(div.style.left) - 5 + "px";
break;
case 39: // 键盘右键
div.style.left = parseInt(div.style.left) + 5 + "px";
break;
}
}
</script>
// 涂色
<ul>
<li img-data="0"></li>
...........很多行
<li img-data="0"></li>
<li img-data="0"></li>
</ul>
<script>
var ul = document.getElementsByTagName('ul')[0];
ul.onmouseover = function(e) {
var event = e || window.event;
var target = event.target || event.srcElement; // 考虑兼容性
target.style.backgroundColor = "rgb(0, 255," + target.getAttribute('img-data') + ")";
// getAttribute() 方法返回指定属性名的属性值
// setAttribute() 方法添加指定的属性,并为其赋指定的值。如果这个指定的属性已存在,则仅设置/更改值。
target.setAttribute('img-data', parseInt(target.getAttribute('img-data')) + 60);
}
</script>
<style>
* {
margin: 0;
padding: 0;
}
ul {
width: 400px;
height: 400px;
margin: 0 auto; /* 水平居中 */
top: 50%;
transform: translateY(50%); /* 垂直居中 */
}
li {
box-sizing: border-box; /* 盒模型, 虽然设置宽高10px,但因为有border,实际宽高8 */
float: left;
width: 10px;
height: 10px;
border: 1px solid black;
list-style: none; /* 去掉li前面的小圆点 */
}
</style>
<div>
<strong></strong>
</div>
<div>
<span>
<strong class="demo">123</strong>
</span>
</div>
<script>
// 缺点: 不实时
// 返回的直接就是<strong class="demo">123</strong>
var strong = document.querySelector('div > span strong.demo');
var strong1 = document.querySelectorAll('div > span strong.demo'); // 返回的是类数组
</script>
遍历节点树
- parentNode 父节点(最顶端的parentNode为#document)
- childNodes 子节点们
- firstChild 第一个子节点
- lastChild 最后一个子节点
- nextSibling 后一个兄弟节点
- previousSibling 前一个兄弟节点
<div>
123不管写多少与注释同算作文本节点-text
<!-- comment -->
<strong></strong>
<span></span>
</div>
<script>
var div = document.getElementsByTagName('div')[0];
console.log(div.childNodes); // NodeList(7) [text, comment, text, strong, text, span, text]
console.log(div.firstChild); // 123不管写多少与注释同算作文本节点-text
console.log(div.lastChild); // #text (没有内容是空格)
console.log(div.firstChild.nextSibling); // <!-- comment -->
console.log(div.lastChild.previousSibling); // <span></span>
</script>
基于元素节点树的遍历(没有文本节点,document等)
- parentElement 返回当前元素的父元素节点(IE9及以下不兼容)
- children 只返回当前元素的元素子节点
- node.childElementCount === node.children.length 当前元素节点的子元素节点个数(IE9及以下不兼容)
- firstElementChild 返回第一个元素节点(IE9及以下不兼容)
- lastElementChild 返回最后一个元素节点(IE9及以下不兼容)
- nextElementSibling 返回后一个兄弟元素节点(IE9及以下不兼容)
- previousElementSibling 返回前一个兄弟元素节点(IE9及以下不兼容)
<div>
123不管写多少与注释同算作文本节点-text
<!-- comment -->
<strong></strong>
<span></span>
</div>
<script>
var div = document.getElementsByTagName('div')[0];
console.log(div.parentElement); // <body>....</body> ---> html ---> null
console.log(div.children); // HTMLCollection(2) [strong, span]
console.log(div.childElementCount); // 2
console.log(div.firstElementChild); // <strong></strong>
console.log(div.lastElementChild); // <span></span>
console.log(div.nextElementSibling); // <scrip type="text/javascript">...</scrip>
console.log(div.previousElementSibling); // null
</script>
节点类型
-
元素节点 — 1(nodeType返回值)
-
属性节点 — 2
-
文本节点 — 3
-
注释节点 — 8
-
document — 9
-
DocumentFragment — 11
获取节点类型 — nodeType
节点的四个属性
-
nodeName 元素的标签名,以大写形式表示,只读
-
nodeValue text节点或comment节点的文本内容,可读写
-
nodeType 节点类型,只读
-
attributes element节点的属性集合
节点的一个方法 Node.hasChildNodes()
<div>
123不管写多少与注释同算作文本节点-text
<!-- comment -->
<strong></strong>
<span></span>
</div>
<script>
var div = document.getElementsByTagName('div')[0];
console.log(div.childNodes[0].nodeName); // #text
console.log(div.childNodes[1].nodeName); // #comment
console.log(div.childNodes[3].nodeName); // STRONG
console.log(div.childNodes[0].nodeValue); // 123不管写多少与注释同算作文本节点-text
console.log(div.childNodes[1].nodeValue); // comment
console.log(div.childNodes[3].nodeValue); // null
console.log(div.childNodes[0].nodeType); // 3 -- 文本节点
console.log(div.childNodes[1].nodeType); // 8 -- 注释节点
console.log(div.childNodes[3].nodeType); // 1 -- 元素节点
</script>
<div id="only" class="one"></div>
<script type="text/javascript">
var div = document.getElementsByTagName('div')[0];
console.log(div.attributes); // NamedNodeMap {0: id, 1: class, id: id, class: class, length: 2}
console.log(div.attributes[0]); // id="only"
console.log(div.attributes[0].nodeType); // 2
console.log(div.attributes[0].value); // only 可以更改
console.log(div.attributes[0].name); // id 不可更改
console.log(div.hasChildNodes()); // false
</script>
<div>
123不管写多少与注释同算作文本节点-text
<!-- comment -->
<strong></strong>
<span></span>
</div>
<script>
// 返回所有的元素节点
function retElementChild(node) {
var temp = {
length: 0,
push: Array.prototype.push, // 不加splice返回: {0: strong, 1: span, length: 2, push: ƒ}
splice: Array.prototype.splice // 让类数组的样式看起来更像数组
},
child = node.childNodes,
len = child.length;
for(var i = 0; i < len; i++) {
if(child[i].nodeType === 1) {
temp.push(child[i]);
}
}
return temp;
}
console.log(retElementChild(div)); // Object(2) [strong, span, push: ƒ, splice: ƒ]
</script>
DOM结构树(继承关系)
|----Document --- HTMLDocument |--- HTMLHeadElement
| |
| |--- HTMLBodyElement
| |--Text |
|--- CharacterData --- | |--- HTMLTitleElement
Node-| |--Comment |
| |--- HTMLParagraphElement
| |
|----Element --- HTMLElement ------- |--- HTMLInputElement
| |
| |--- HTMLTableElement
| |
|--- Attr |--- .etc
document.__proto__ ---> HTMLDocument
document.__proto__.__proto__ ---> Document {…}
document.__proto__.__proto__.__proto__ ---> Node {…}
document.__proto__.__proto__.__proto__.__proto__ ---> EventTarget
document.__proto__.__proto__.__proto__.__proto__.__proto__ ---> Object {...constructor: ƒ,…}
document.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__ ---> null
1、getElementById方法定义在Document.prototype上,即Element节点上不能使用
2、getElementsByName方法定义在HTMLDocument.prototype上,即非html中的document不能使用(xml document、Element)
3、getElementsByTagName方法定义在Document.prototype和Element.prototype上
4、HTMLDocument.prototype定义了一些常用的属性,body,head分别指代HTML文档中的标签
5、Document.prototype定义了documentElement属性,指代文档的根元素,在HTML文档中,指代标签
6、getElementsByClassName、querySelectorAll、querySelector在Document.prototype,Element.prototype类中均有定义
作业:
1、遍历元素节点树(在原型链上编程)
2、封装函数,返回元素e的第n层祖先元素节点
<div>
123不管写多少与注释同算作文本节点-text
<!-- comment -->
<strong></strong>
<span></span>
<em></em>
<i></i>
<b></b>
</div>
<script>
function retParentElementNode(elem, n) {
while(elem && n) {
elem = elem.parentElement;
n--;
}
return elem;
}
var em = document.getElementsByTagName('em')[0];
console.log(retParentElementNode(em, 2)); // <body>...</body>
console.log(retParentElementNode(em, 3)); // <html>...</html>
console.log(retParentElementNode(em, 4)); // null
console.log(retParentElementNode(em, 0)); // <em></em>
console.log(retParentElementNode(em, -1)); // null
</script>
3、封装函数,返回元素e的第n个兄弟元素节点,n为正,返回后面的兄弟元素节点;n为负,返回前面的;n为0,返回自己
<div>
123不管写多少与注释同算作文本节点-text
<!-- comment -->
<strong></strong>
<span></span>
<em></em>
<i></i>
<b></b>
<!-- comment -->
</div>
<script>
function retSibling(elem, n) {
while(elem && n) {
if(n > 0) {
if(elem.nextElementSibling) {
elem = elem.nextElementSibling; // IE9以下不兼容
} else {
// for(elem = elem.nextSibling;elem && elem.nodeType != 1;elem = elem.nextSibling);
do {
elem = elem.nextSibling;
} while(elem && elem.nodeType != 1)
}
n--;
} else {
if(elem.previousElementSibling) {
elem = elem.previousElementSibling; // IE9以下不兼容
} else {
// for(elem = elem.previousSibling;elem && elem.nodeType != 1;elem = elem.previousSibling);
do {
elem = elem.previousSibling;
} while(elem && elem.nodeType != 1)
}
n++;
}
}
return elem;
}
var em = document.getElementsByTagName('em')[0];
console.log(retSibling(em, -300)); // null
console.log(retSibling(em, -3)); // null
console.log(retSibling(em, -2)); // <strong></strong>
console.log(retSibling(em, 0)); // <em></em>
console.log(retSibling(em, 2)); // <b></b>
console.log(retSibling(em, 3)); // null
console.log(retSibling(em, 300)); // null
</script>
4、编辑函数,封装myChildren功能,解决以前部分浏览器的兼容性问题(子元素节点,实现children功能)
<div>
123不管写多少与注释同算作文本节点-text
<!-- comment -->
<strong></strong>
<span>
<i></i>
<strong>
<i></i>
</strong>
</span>
<em>...</em>
<b></b>
</div>
<script>
// 原型链上封装
Element.prototype.myChildren = function() {
var child = this.childNodes,
len = child.length,
arr = [];
for(var i = 0; i < len; i++) {
if(child[i].nodeType == 1) {
arr.push(child[i]);
}
}
return arr
}
var span = document.getElementsByTagName('span')[0];
var em = document.getElementsByTagName('em')[0];
console.log(span.myChildren()); // [i, strong]
console.log(em.myChildren()); // []
</script>
5、封装hasChildren()方法,不可用children属性(hasChildNodes方法)
<div>
123不管写多少与注释同算作文本节点-text
<!-- comment -->
<strong></strong>
<span>
<i></i>
</span>
<em>...</em>
<b></b>
</div>
<script>
function hasChildren(elem) {
var child = elem.childNodes,
len = child.length;
for(var i = 0; i < len; i++) {
if(child[i].nodeType == 1) {
return true;
}
}
return false;
}
var span = document.getElementsByTagName('span')[0];
var em = document.getElementsByTagName('em')[0];
var b = document.getElementsByTagName('b')[0];
console.log(hasChildren(span)); // true
console.log(hasChildren(em)); // false
console.log(hasChildren(b)); // false
</script>
增
- document.createElement(); 创建元素节点
- document.createTextNode(); 创建文本节点
- document.createComment(); 创建注释节点
- document.createDocumentFragment(); 创建文档碎片节点
插
-
parentNode.appendChild(); 剪切操作:之前在一个位置,下次添加到另一个位置,则原位置的就没了
------ document.body.appendChild(div);
-
parentNode.insertBefore(a, b); 在b之前插入a
删
- parent.removeChild(); 删除子元素
- child.remove(); es5新方法,删除自身
替换
- parent.replaceChild(new, origin);
element节点的一些 属性 方法
dom.className可以读写class
属性
-
innerHTML — div.innerHTML 获取当前div的HTML结构 div.innerHTML = "…"写HTML结构,原来的结构被覆盖
div.innerHTML = "123"也可以生效
-
innerText 火狐不兼容,火狐用textContent,但是老IE不兼容
方法
- ele.setAttribute(‘属性名’, ‘属性值’); div.setAttribute(‘class’, ‘demo’) ---->
…
- ele.getAttribute(); div.getAttribute(‘class’); —> demo
作业:
1、用js脚本生成下面的DOM结构。
使用标准的DOM属性或方法。
<div class="example">
<p class="slogan">
hahahahhahahahaha
</p>
</div>
//
<script>
var div = document.createElement('div');
document.body.appendChild(div);
div.setAttribute('class', 'example');
var p = document.createElement('p');
div.appendChild(p);
p.setAttribute('class', 'slogan');
p.innerHTML = 'hahahahhahahahaha';
</script>
2、封装函数insertAfter(),功能类似insertBefore()
可忽略老版本浏览器,直接在Element.prototype上编程
<div>
<span></span>
12233
<!--jajhaghq-->
<em></em>
<strong></strong>
<i></i>
</div>
<script>
// 在b之后插入a
Element.prototype.insertAfter = function(a, b) {
var beforeNode = b.nextElementSibling;
if(beforeNode) {
// b.parentElement.insertBefore(a, beforeNode);
this.insertBefore(a, beforeNode); // 在原型链上编程this可以指代任何要调用的对象,直接封装function,this要变成一个参数
} else {
this.appendChild(a);
}
}
var span = document.getElementsByTagName('span')[0];
var p = document.createElement('p');
div.insertAfter(p, span);
</script>
3、将目标节点内部的节点顺序逆序
eg:
<div>
<span>
<strong></strong>
<em></em>
</span>
<b></b>
<strong></strong>
<i></i>
<em></em>
</div>
<script>
function reverse(elem) {
var chi = elem.children,
result = [],
len = chi.length;
for(var i = 0; i < len/2; i++) {
result[i] = chi[len-1-i];
result[len-1-i] = chi[i];
}
return result;
}
var div = document.getElementsByTagName('div')[0];
var span = document.getElementsByTagName('span')[0];
console.log(reverse(div)); // [em, i, strong, b, span]
console.log(reverse(span)); // [em, strong]
</script>
// appendChild
<script>
function reverse(elem) {
var chi = elem.children,
len = chi.length,
length = 0;
if(len % 2 == 0) {
length = len/2;
} else {
length = len/2 + 1;
}
for(var i = 0; i < length; i++) {
elem.appendChild(chi[len-2-i]);
}
return chi;
}
</script>
17 Date()日期对象
作业
1、封装函数,打印当前的年月日时分秒
function format(num) {
num = num < 9 ? '0' + num : num;
return num;
}
var nowDate = new Date();
var year = nowDate.getFullYear();
var month = nowDate.getMonth()+1;
var day = nowDate.getDate();
var hour = nowDate.getHours();
var min = nowDate.getMinutes();
var second = nowDate.getSeconds();
var date = year+':'+format(month)+':'+format(day) +' '+format(hour)+':'+format(min)+':'+format(second);
console.log(date);
18 定时器
-
setInterval(function(){},1000),其返回值作为定时器的唯一标识,在凊除定时器时传唯一标识就行,一般定义一个变量接收唯一标识
第一个参数可以不传函数,直接传字符串也可以执行,比如:setInterval(“console.log(‘a’)”,1000)
-
setTimeout()只执行一次,同时出现,唯一标识不会与setInterval重复
-
clearInterval()
-
clearTimeout()
全局对象window上的方法,内部函数this指向window
作业
1、写个计时器,3分钟停止
<style>
input {
border: 1px solid rgb(0,0,0,0.8);
text-align: right;
font-size: 16px;
font-weight: bold;
}
</style>
minutes: <input type="text" value="0" />
seconds: <input type="text" value="0" />
<script>
var input1 = document.getElementsByTagName('input')[0];
var input2 = document.getElementsByTagName('input')[1];
var minutes = 0,
seconds = 0;
var timer = setInterval(function() {
seconds++;
if(seconds == 60) {
minutes++;
seconds = 0;
}
if(minutes == 3) {
clearInterval(timer);
}
input1.setAttribute('value', minutes); // input1.value = minutes;
input2.setAttribute('value', seconds); // input2.value = seconds;
}, 1000)
</script>
19 dom基本操作
查看滚动条的滚动距离
- window.pageXOffset/pageYOffset (IE8及以下不兼容)
- document.body.scrollLeft/scrollTop
- document.documentElement.scrollLeft/scrollTop (兼容性混乱,用时与document.body.scrollLeft/scrollTop相加,因为不可能存在两个同时有值)
作业
1、封装兼容性方法getScrollOffset(),求滚动距离
function getScrollOffset() {
if(window.pageXOffset) {
return {
x: window.pageXOffset,
y: window.pageYOffset
}
} else {
return {
x: document.body.scrollLeft + document.documentElement.scrollLeft,
y: document.body.scrollTop + document.documentElement.scrollTop
}
}
}
查看视口的尺寸
- window.innerWidth/innerHeight (IE8及以下不兼容)
- document.documentElement.clientWidth/clientHeight (标准模式下,任意浏览器都兼容)
- document.body.clientWidth/clientHeight (适用于怪异模式下的浏览器,去掉)
作业
1、封装兼容性方法getViewportOffset(),返回浏览器视口尺寸
查看当前浏览器模式document.compatMode CSS1Compat标准模式 BackCompat怪异模式
function getViewportOffset() {
if(window.innerWidth) {
return {
width: window.innerWidth,
height: window.innerHeight
}
} else {
if(document.compatMode === 'CSS1Compat') {
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
}
} else {
return {
width: document.body.clientWidth,
height: document.body.clientHeight
}
}
}
}
查看元素的几何尺寸
- domElement.getBoundingClientRect() es5新方法 兼容性好,返回对象,里面有left、top、right、bottom等属性。
left,top代表该元素左上角的X和Y坐标,right和bottom代表元素右下角的X和Y坐标
width和height属性老版本IE并未实现,返回的是加上padding之后的宽高,不包括margin,非内容的宽高
返回结果非实时
查看元素的尺寸
- dom.offsetWidth/offsetHeight 返回的是加上padding之后的宽高,不包括margin,非内容的宽高
查看元素的位置
- dom.offsetLeft/offsetTop
对于无定位父级的元素,返回相对文档的坐标。对于有定位父级的元素,返回相对于最近的有定位的父级的坐标
- dom.offsetParent
返回最近的有定位的父级,如无则返回body,
body.offsetParent 返回null
作业
1、求元素相对于文档的坐标getElementPosition()
function getElementPosition(elem) {
var xoffset = 0, yoffset = 0;
while(elem.offsetParent) {
xoffset += elem.offsetLeft;
yoffset += elem.offsetTop;
elem = elem.offsetParent;
}
return {
x: xoffset,
y: yoffset
}
}
让滚动条滚动
- scroll(x, y)
- scrollTo(x, y)
- scrollBy(x, y)
三个方法功能类似,用法都是将x,y坐标传入。让滚动条滚动到当前位置
区别: scrollBy会在之前的数据上累加
作业
1、利用scrollBy实现快速阅读的功能
<div>.........................</div>
<div class="demo" style="background-color: red;bottom: 190px;">start</div>
<div class="demo" style="background-color: blue;bottom: 120px;">加速</div>
<div class="demo" style="background-color: green;bottom: 50px;">stop</div>
<script>
var start = document.getElementsByTagName('div')[1];
var speed = document.getElementsByTagName('div')[2];
var stop = document.getElementsByTagName('div')[3];
var timer = 0, sp = 10,key = true;
start.onclick = function () {
if(key) {
timer = setInterval(function () {
window.scrollBy(0, sp);
}, 100);
key = false;
}
}
speed.onclick = function() {
sp += 10;
},
stop.onclick = function() {
clearInterval(timer);
key = true;
}
</script>
20 脚本化CSS
读写元素css属性
- dom.style.prop
- 可读写行间样式,没有兼容性问题,碰到float这样的保留字属性,前面应加css。eg: float —> cssFloat
- 复合属性必须拆解border,font,组合单词变成小驼峰式写法background-color —> backgroundColor
- 写入的值必须是字符串格式
查询计算样式
- window.getComputedStyle(ele, null); 获取到的是显示出来的值或者默认值,多次赋值只显示最后成功的那个值
- 计算样式只读
- 返回的计算样式的值都是绝对值,没有相对单位
- IE8及以下不兼容
<style>
/* 伪元素 */
div::after {
content: '';
width: 50px;
height: 50px;
background-color: pink;
display: inline-block;
}
.yellow::after {
background-color: yellow;
}
.green::after {
background-color: green;
}
</style>
<div style="height: 100px;background-color: red;"></div>
<script>
var div = document.getElementsByTagName('div')[0];
// 唯一获取伪元素的样式表方法
window.getComputedStyle(div, 'after');
// 改变伪元素的颜色
var count = 0;
div.onclick = function() {
if(count % 2 == 0) {
div.className = 'yellow';
} else {
div.className = 'green';
}
count++;
}
</script>
查询样式
- ele.currentStyle
- 计算样式只读
- 返回的计算样式的值不是经过转换的绝对值
- IE独有的特性
作业
1、封装兼容性方法getStyle(elem, prop); 查询计算样式
function getStyle(elem, prop) {
if(window.getComputedStyle) {
return window.getComputedStyle(elem, null)[prop];
} else {
return elem.currentStyle[prop];
}
}
2、方块运动
<div style="width: 100px;height: 100px;background-color: red;position: absolute;left: 0;"></div>
<script>
function getStyle(elem, prop) {
if(window.getComputedStyle) {
return window.getComputedStyle(elem, null)[prop];
} else {
return elem.currentStyle[prop];
}
}
var div = document.getElementsByTagName('div')[0];
var speed = 1;
var timer = setInterval(function() {
speed += speed/10;
div.style.left = parseInt(getStyle(div, 'left')) + speed + 'px';
if(parseInt(div.style.left) > 500) {
clearInterval(timer);
}
}, 10);
</script>
3、轮播图
21 事件
交互体验的核心功能
演示demo 拖拽和点击
如何绑定事件处理函数
-
1、ele.onxxx = function (event) {}
- 兼容性好,但是一个元素的同一个事件上只能绑定一个处理程序
- 基本等同于写在HTML行间上 ----- onxxx=“函数里面的语句”
-
2、obj.addEventListener(type事件类型, fn处理函数, false);
div.addEventListener('click', function() { console.log('a'); }, false); div.addEventListener('click', function() { console.log('a'); }, false); // 最后会输出两个a // 或者 div.addEventListener('click', test, false); // 第二个参数是函数引用 div.addEventListener('click', test, false); // 最后只会输出一个a function test() { console.log('a'); }
- IE9以下不兼容,可以为一个事件绑定多个处理函数(处理函数都用test表示,则只执行一次,除非写成test函数体,才会执行两次)
-
3、obj.attachEvent(‘on’ + type, fn);
- IE独有,一个事件同样可以绑定多个处理函数(同一个函数绑定多次会执行多次)
作业
1、使用原生js, addEventListener,给每个li元素绑定一个click事件,输出它们的顺序
<ul>
<li>a</li>
<li>a</li>
<li>a</li>
<li>a</li>
</ul>
<script>
var lis = document.getElementsByTagName('li');
for(var i = 0; i < lis.length; i++) {
// 绑定事件注意是否形成闭包
(function (j) {
lis[j].addEventListener('click', function() {
console.log(j);
}, false);
})(i);
}
</script>
事件处理程序的运行环境
- 1、ele.onxxx = function(event) {}
- 程序this指向是dom元素本身
- 2、obj.addEventListener(type事件类型, fn处理函数, false);
- 程序this指向是dom元素本身
- 3、obj.attachEvent(‘on’ + type, fn);
- 程序this指向window
作业
1、封装兼容性的addEvent(elem, type, handle);方法 绑定事件处理函数
function addEvent(elem, type, handle) {
if(elem.addEventListener) {
elem.addEventListener(type, handle, false);
} else if(elem.attachEvent) {
elem.attachEvent('on' + type, function() {
handle.call(elem); // 改变this指向为elem
});
} else {
elem['on' + type] = handle;
}
}
解除事件处理程序
- ele.onclick = false null ‘’
- ele.removeEventListener(type, fn, false);
- ele.detachEvent(‘on’ + type, fn);
若绑定匿名函数,则无法解除
封装removeEvent:
function removeEvent(elem, type, handle) {
if(elem.removeEventListener) {
elem.removeEventListener(type, handle, false);
} else if(elem.detachEvent) {
elem.detachEvent('on' + type, function() {
handle.call(elem);
});
} else {
elem['on' + type] = null;
}
}
事件处理模型—事件冒泡、事件捕获
-
事件冒泡
- 结构上(非视觉上)嵌套关系的元素,会存在事件冒泡的功能,即同一事件,自子元素冒泡向父元素。(自底向上)
-
事件捕获(addEventListener的第三个参数为true; div.setCapture(); 捕获页面上发生的所有事件算是自己的 div.releaseCapture(); )
- 结构上(非视觉上)嵌套关系的元素,会存在事件捕获的功能,即同一事件,自父元素捕获至子元素(事件源元素)。(自顶向下)
- IE没有捕获事件
-
触发顺序
- 先捕获,后冒泡
-
focus、blur、change、submit、reset、select等事件不冒泡
<div class="wrapper">
<div class="content">
<div class="box"></div>
</div>
</div>
<script>
var wrapper = document.getElementsByClassName('wrapper')[0];
var content = document.getElementsByClassName('content')[0];
var box = document.getElementsByClassName('box')[0];
// 点击box时,依次打印出 box冒泡、content冒泡、wrapper冒泡(结构上自底向上冒泡)
wrapper.addEventListener('click', function() {
console.log('wrapper冒泡');
}, false);
content.addEventListener('click', function() {
console.log('content冒泡');
}, false);
box.addEventListener('click', function() {
console.log('box冒泡');
}, false);
// 事件捕获 addEventListener的第三个参数变为true
// 点击box时,依次打印出 wrapper捕获、content捕获、box捕获(结构上自顶向下捕获)
wrapper.addEventListener('click', function() {
console.log('wrapper捕获');
}, true);
content.addEventListener('click', function() {
console.log('content捕获');
}, true);
box.addEventListener('click', function() {
console.log('box捕获');
}, true);
// 整体打印出 wrapper捕获、content捕获、box捕获、box冒泡、content冒泡、wrapper冒泡
// 火狐打印结果: wrapper捕获、content捕获、box冒泡、box捕获、content冒泡、wrapper冒泡
// 点击box,box此时是事件执行,先绑定的先执行。box冒泡先绑定的应该先输出,再是box捕获
</script>
取消冒泡、阻止默认事件
-
取消冒泡
- W3C标准event.stopPropagation();但不支持IE9以下版本
- IE独有 event.cancelBubble = true; 谷歌也实现了
-
阻止默认事件
- 默认事件 — 表单提交, a标签跳转,右键菜单等. 右键出菜单document.oncontextmenu
- 1、return false; 以对象属性的方式注册的事件才生效eg: div.οnclick=function(){}
- 2、event.preventDefault(); W3C标注,IE9以下不兼容
- 3、event.returnValue = false; 兼容IE
// a标签取消跳转
<a href="javascript:void(false)">demo</a>
// void(false)就相当于return false
作业
1、封装取消冒泡的函数 stopBubble(event)
function stopBubble(event) {
if(event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
2、封装阻止默认事件的函数cancelHandler(event);
function cancelHandler(event) {
if(event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
}
事件源对象
事件对象:event || window.event 用于IE
事件源对象: 在点击事件时可以得出点击的是哪个
- event.target 火狐只有这个
- event.srcElement IE只有这个
- chrome都有
事件委托
利用事件冒泡和事件源对象进行处理
优点:
1、性能 不需要循环所有的元素一个个绑定事件
2、灵活 当有新的子元素时不需要重新绑定
作业
1、 点击每个li时打印出里面的值
var ul = document.getElementsByTagName('ul')[0];
ul.onclick = function (e) {
var event = e || window.event;
var target = event.target || event.srcElement;
console.log(target.innerText);
}
2、拖拽方块
function drag(elem) {
var disX, disY;
addEvent(elem, 'mousedown', function(e) {
var event = e || window.event;
disX = event.clientX - parseInt(getStyle(elem, 'left'));
disY = event.clientY - parseInt(getStyle(elem, 'top'));
addEvent(document, 'mousemove', mouseMove);
addEvent(document, 'mouseup', mouseUp);
stopBubble(event);
cancelHandler(event);
});
function mouseMove(e) {
var event = e || window.event;
elem.style.left = event.clientX - disX + 'px';
elem.style.top = event.clientY - disY + 'px';
}
function mouseUp(e) {
var event = e || window.event;
removeEvent(document, 'mousemove', mouseMove);
removeEvent(document, 'mouseup', mouseUp);
}
}
事件分类
鼠标事件:
click、mousedown、mousemove鼠标移动、mouseup、contextmenu右键出菜单、mouseover、mouseout、mouseenter(h5新规范)、mouseleave(h5新规范)
移动端:touchstart touchmove touchend
DOM3标准规定: click事件只能监听左键,只能通过mousedown和mouseup来判断鼠标键。
用e.button来区分鼠标的按键: 0-左键 1-滚轮 2-右键
// 区分鼠标按键
document.onmousedown = function(e) {
console.log(e.button);
}
document.onmouseup = function(e) {
console.log(e.button);
}
如何解决mousedown和click的冲突: click = mousedown + mouseup
区分是拖拽还是点击,看mousedown与mouseup的时间差:
var firstTime = 0, lastTime = 0, key = false;
document.onmousedown = function() {
firstTime = new Date().getTime();
}
document.onmouseup = function() {
lastTime = new Date().getTime();
if(lastTime - firstTime < 300) {
key = true;
}
}
document.onclick = function() {
if(key) {
console.log('click');
key = false;
}
}
键盘事件:
keydown > keypress > keyup 顺序
keydown 与 keypress 的区别:
- keydown 可以响应任意键盘按键, keypress只可以响应字符类键盘按键
- keypress返回ASCII码,可以转换成相应字符( String.formCharCode(e.charCode) )
文本操作事件:
input、focus、blur、change
input.oninput 只要文本框里面值发生变化就触发,不用等失焦
input.onchange 只有失焦之后且文本框前后值发生变化才触发
窗体操作类(window上的事件):
scroll load
作业
1、fixed定位 js兼容版
2、完善轮播图,加按钮 正反播放
3、提取密码框的密码 监听键盘事件
4、输入框功能完善
5、二级菜单栏
6、贪吃蛇
7、扫雷
22 json
json是一种传输数据的格式(以对象为样板,本质上就是对象,但用途有区别,对象是本地用的,json使用来传输的)
JSON.parse(); string ----> json
JSON.stringify(); json ----> string
页面渲染:
dom树 + css树 = renderTree
renderTree会发生 重排reflow (效率低) 或者 重绘repaint
reflow: dom节点的删除、添加
dom节点的宽高变化、位置变化, diaplay:none —> block等
offsetWidth offsetLeft
repaint:css背景图片改变,颜色变化等
异步加载js
js加载缺点: 加载工具方法没必要阻塞文档,js加载太过会影响页面效率,一旦网速不好,整个网站将等待js加载而不进行后续渲染等工作
有些工具方法需要按需加载,用到时加载,不用不加载
异步加载的三种方案:
- defer 异步加载,要等到dom文档全部解析完才会被执行。只有IE能用,也可以将代码写到内部
<script defer>代码</script>
- async 异步加载,加载完就执行,async只能加载外部脚本,不能把js写在script标签里
<script async src="xxx.js"></script>
- defer、async执行时也不阻塞页面
- 3、创建script,插入到DOM中,加载完毕后callBack回调函数
function loadScript预解析时不会执行函数里面的代码,然后解析(url, callback) {
var script = document.createElement('script');
script.type = "text/javascript";
if(script.readyState) {
// IE方法
script.onreadystatechange = function() {
if(script.readyState == "complete" || script.readyState == "loaded") {
callback();
}
}
} else {
script.onload = function() { // IE无load函数
callback(); // 下载的url文件里面的函数,等到文件加载完再执行
}
}
script.src = url; // 只是下载 先绑定事件再加载,防止已经加载完毕readyState已经从loading变为loaded,此时readystatechange不会执行,后面的就不会执行了
document.head.appendChild(script); // 推到head或body等才会开始执行
}
loadScript('xxx.js', test);// 直接写函数名会报错test未定义,因为function loadScript预解析时不会执行函数里面的代码,然后解析loadScript('xxx.js', test);就会报错
loadScript('xxx.js', function() {
test(); // 这样写预解析时不会执行test,正式执行时上面的也已经加载完成了,不会报错
});
loadScript('xxx.js', "test()") // 这样写的话callback()----> eval(callback) 也可以执行
// eval把字符串当做代码来执行
loadScript('xxx.js', "test"); // callback()----> tools[callback]();
// xxx.js中函数需按照一定的格式写
var tools = {
test: function() {...},
demo: function() {...}
}
js加载时间线
- 1、创建document对象,开始解析web页面。document.readyState =“loading” 解析HTML元素和文本内容后添加element对象和text节点到文档中
- 2、遇到link外部 css,创建线程加载,并继续解析文档
- 3、遇到script外部js 无defer、async,浏览器加载,并阻塞,等js加载完成并执行该脚本,然后继续解析文档
- 4、遇到script外部js 有defer、async,浏览器创建线程加载,并继续解析文档。async脚本加载完之后立即执行,defer等js加载完成之后执行(异步禁止使用document.write(),等js加载完成之后即load中执行document.write,里面的代码会覆盖之前的页面)
- 5、遇到img等,先正常解析dom结构,然后浏览器异步加载src,并继续解析文档。
- 6、当文档解析完成,document.readyState =“interactive”
- 7、文档解析完成后,所有设置defer的脚本按照顺序执行。
- 8、document对象触发DOMContentLoaded事件,程序执行从“同步脚本执行阶段”转为”事件驱动阶段“
- 9、当所有async脚本加载完成并执行后,img等加载完成后,document.readyState =“complete”,window对象触发load事件
- 10、以异步响应方式处理用户输入、网络事件等。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script>
// 模拟jquery的$(document).ready方法
document.addEventListener('DOMContentLoaded', function() {
var div = document.getElementsByTagName('div')[0];
}, false);
</script>
</head>
<body>
<div style="width: 100px;height: 100px;background-color: greenyellow;"></div>
<script>
// 执行结果
// loading --- Uncaught ReferenceError: $ is not defined --- interactive --- a --- complete
console.log(document.readyState); // loading
document.onreadystatechange = function() {
console.log(document.readyState); // 先interactive 再 complete
}
document.addEventListener('DOMContentLoaded', function() {// 只能采用addEventListener方法,document.onxxx方法不可取
console.log('a'); // 先于complete执行,文档解析完成出现
}, false);
// 当dom解析完就执行的部分 jquery方法
$(document).ready(function() {
console.log('b'); // dom解析完就执行,不必等到load阶段
})
</script>
</body>
</html>
23 正则表达式
转义字符 \
字符串换行符 \n
\r 行结束符
\t table一个缩进
正常情况一个回车是\r\n,linux系统里回车是\n
多行字符串,在每行末尾加\
RegExp正则表达式作用: 匹配特殊字符或有特殊搭配原则的字符的最佳选择
两种创建方式
- 直接量
- new RegExp(“字符串”, “类型gim等”);
/…/img i: ignoreCase 忽略大小写 m: 多行匹配 g: 全局匹配(查找所有匹配而非在找到第一个匹配后停止)
^以后面的字符开头,但是放在表达式[]里面表示 “非”
var reg = /abc/m;
var reg1 = new RegExp(reg);
var reg2 = RegExp(reg);
// reg1与reg不指向同一个引用, reg.abc = 123;reg1.abc = undefined;
// reg2与reg指向同一个引用, reg.abc = 123;reg2.abc = 123;
// 全局匹配
var reg = /ab/;
var reg1 = /ab/g;
var str = 'abababababab';
str.match(reg); // ["ab"] match返回匹配的字符
reg.test(str); // true test返回匹配结果
str.match(reg1); // ["ab", "ab", "ab", "ab", "ab", "ab"]
// 多行匹配
var reg = /^a/g;
var reg1 = /^a/gm;
var str = 'abcd\na'; // \n换行,所有有两个以a开头的字段
str.match(reg); // ["a"] 虽然换行了,但未多行匹配,所以只能输出一个a
str.match(reg1); // ["a", "a"]
[]
[可以取的范围]表达式, 查找某个范围内的字符。一个[]代表一位
[adgk] 查找给定集合内的任何字符
[^adgk] 查找给定集合外的任何字符
(abc|bcd|cde) 查找任何指定的选项 — str是abc,bcd,cde都可以返回true,|表示或
var reg = /(abc|bcd)[0-9]/g;
var str = 'abcd3abc5';
str.match(reg); // ["bcd3", "abc5"]
var reg = /([a-z]|[0-9])[0-9]/g; // 等同于 var reg = /[a-z0-9][0-9]/g;
var str = 'abcd3abc587345';
str.match(reg); // ["d3", "c5", "87", "34"]
元字符
拥有特殊含义的字符
元字符 | 描述 |
---|---|
\d | 查找数字。[0-9] |
\D | [^\d] |
\s | 查找空白字符。[\t\r\n\v\f ] 空格符在reg里面直接空格就行、制表符\t、回车符\r、换行符\n、垂直换行符\v、换页符\f |
\S | 查找非空白字符。[ ^\s] |
\b | 匹配单词边界。 str = ‘abc bcd cde’ 有六个单词边界a,c,b,d,c,e。reg=/\bcde/g; match出cde |
\w | [0-9A-z_] |
\W | [^\w] |
. | 查找单个字符,除了换行和行结束符。[ ^\r\n] |
\0 | 查找NUL字符 |
\xxx | 查找以八进制数 xxx 规定的字符。 |
\xdd | 查找以十六进制数 dd 规定的字符。 |
\uxxxx | 查找以十六进制数 xxxx 规定的 Unicode 字符。 |
量词
量词 | 描述 |
---|---|
n+ | 匹配任何包含至少一个 n 的字符串。 {1, 无穷} 贪婪匹配 能多不少 在量词后面加?即 n+? 变成非贪婪匹配,能少不多 |
n* | 匹配任何包含零个或多个 n 的字符串。 {0, 无穷} n*?能匹配0个不取多 |
n? | 匹配任何包含零个或一个 n 的字符串。 {0, 1} n??能匹配0个不取多 |
n{x} | {x} |
n{x, y} | {x, y} n{x, y}? 能取x个不取x之后到y的 |
n{x,} | {x, 无穷} |
n$ | 匹配任意结尾为 n 的字符串 |
^n | 匹配任意开头为 n 的字符串 |
?=n | 匹配任何其后紧接指定字符串 n 的字符串 正向预查 |
?!n | 匹配任何其后没有紧接指定字符串 n 的字符串 非正向预查 |
var reg = /\w+/g;
var reg1 = /\w*/g;
var reg2 = /\w?/g;
var str = "abc";
str.match(reg); // ["abc"]
str.match(reg1); // ["abc", ""] 匹配到abc后,光标在c之后,也会匹配一个逻辑上的空。如果reg1为/\d*/g,会匹配出四个""
str.match(reg2); // ["a", "b", "c", ""]
"abacaa".match(/a(?=b)/g); // ["a"] 正向预查 正向断言 只找a后面跟着b的a
"abacaa".match(/a(?!b)/g); // ["a", "a", "a"] 非正向预查 只找a后面不跟着b的a
作业:字符串首尾是否含有数字
// 字符串首尾是否含有数字
var reg = /^\d|\d$/g;
var str = "1..,kiuyg3";
str.match(reg); // ["1", "3"]
// 字符串首尾是否都含有数字,中间匹配任意字符0或多个
var reg = /^\d[\s\S]*\d$/g;
RegExp对象方法
test: 检索字符串中指定的值。返回true 或 false
exec: 检索字符串中指定的值。返回找到的值,并确定其位置。index返回的是第一个匹配字符的位置
compile: 编译正则表达式
var reg = /ab/g;
var str = "ababab"; // 0a1b 2a3b 4a5b6 加位置
console.log(reg.lastIndex); // 0 游标位置。exec通过游标位置找下一次匹配。改变reg.lastIndex=2的值,exec值也会相应变化
reg.exec(str); // ["ab", index: 0, input: "ababab", groups: undefined] 类数组
console.log(reg.lastIndex); // 2
reg.exec(str); // ["ab", index: 2, input: "ababab", groups: undefined]
console.log(reg.lastIndex); // 4
reg.exec(str); // ["ab", index: 4, input: "ababab", groups: undefined]
console.log(reg.lastIndex); // 6
reg.exec(str); // null
reg.exec(str); // ["ab", index: 0, input: "ababab", groups: undefined]
reg.compile(str); // /ababab/
作业:匹配形如aabb,aaaa格式的字符串
// 匹配四个一样字符的字符串
var reg = /(\w)\1\1\1/g; // \1反向引用第一个子表达式里面匹配的内容
var str = "aaaaccccggffff";
str.match(reg); // ["aaaa", "cccc", "ffff"]
// 匹配形如aabb,aaaa格式的字符串
var reg = /(\w)\1(\w)\2/g; // \2反向引用第二个子表达式里面匹配的内容
var str = "aaaaccccggffff";
str.match(reg); // ["aaaa", "cccc", "ggff"]
var reg = /(\w)\1(\w)\2/g; // \2反向引用第二个子表达式里面匹配的内容
var str = "aabb";
str.match(reg); // ["aabb"]
reg.exec(str); // ["aabb", "a", "b", index: 0, input: "aabb", groups: undefined]
// 多出来的a,b分别是第一个与第二个子表达式匹配的内容
string对象的方法
match: 找到一个或多个正则表达式的匹配。正则表达式不加g,返回结果同exec的格式;加g只返回匹配值无其他index信息等
search: 检索与正则表达式相匹配的值,返回第一个匹配值的位置。接受字符串作为搜索参数。字符串参数将被转换为正则表达式
replace: 替换与正则表达式匹配的子串,返回正则表达式被替换处修改后的字符串。
split: 把字符串分割为字符串数组,返回不包括正则表达式后的切割字符串数组
var reg = /ab/g;
var str = "ababab";
str.search(reg); // 0 返回第一个匹配值的位置。匹配不到返回-1
str.search("b"); // 1
str.replace(reg, 'cd'); // "cdcdcd" 如果正则表达式不带g,也只能替换第一个
str.split(reg); // ["", "", "", ""]
"abcabdaabe".split(reg); // ["", "c", "da", "e"]
// 普通的replace不能替换全局,只会替换第一个
var str = "ababab";
str.replace("ab", 'cd'); // "cdabab"
作业: aabb 变为 bbaa
// aabb 变为 bbaa
var reg = /(\w)\1(\w)\2/g;
var str = "aabb";
str.replace(reg, "$2$2$1$1"); // "bbaa" $反向引用第几个表达式的内容,如果想替换成$,需要写成$$,第一个$相当于转义字符
str.replace(reg, function($, $1, $2) { // $表示正则表达式匹配的结果, $1第一个子表达式匹配的值
return $2 + $2 + $1 + $1;
})
string.toUpperCase(); // 变大写
string.toLowerCase(); // 变小写
作业: the-first-name变为小驼峰写法theFirstName
// the-first-name变为小驼峰写法theFirstName
var str = "the-first-name";
var reg = /-(\w)/g;
str.replace(reg, function($, $1) {
return $1.toUpperCase(); // "theFirstName"
})
作业:字符串去重
// 字符串去重
var str = "aaabbbbdcccdssrrs";
var reg = /(\w)\1*/g;
str.replace(reg, "$1"); // "abdcdsrs"
作业: 数字变成科学计数的样式 1000000 —> 1.000.000
var str = '1000000';
var reg = /(?=(\B)(\d{3})+$)/g; // 找后面跟三位数字的空格,替换成".",但是第一位不能替换为".",所以空要是非单词边界
str.replace(reg, "."); // "1.000.000"
"100000000".replace(/(?=(\d{3})+$)/g,"."); // ".100.000.000"