IE——trident
Chrome——webkit / blink
Firefox——gecko
Opera——presto
Safari——webkit
a. 原始值:
存放在栈中(stack: first in last out)(不可改变的原始值)
Number Boolean String undefined null
栈相当于内存,内存中的数据不会更改,只会依次往下一个内存单元格里储存,直到内存用 完,才开始进行新一轮覆盖。
原始值存放时把值放在栈中,然后将栈的单元格编号改为变量名。
b. 引用值:
大致存放在堆中(heap)
array Object function date RegExp…
引用值存放时把值放在堆中,把堆的单元格编号放在栈中,形成指向性的指针,然后将栈的单元格编号改为变量名。
function test () {}
for () {}
if () {}
a. &&:先看第一个表达式转换为布尔值的结果,如果为false,则返回第一个表达式的值,如果结果为真,那么它会看第二个表达式转换为布尔值的结果;如果只有两个表达式的话,只要看到第二个表达式,就可以返回该表达式的值了。如果有多个表达式以此类推。
undefined、null、NaN、""、0、false转换为布尔值的结果都为false
var a = 1 && 2;
console.log(a); // 2
1 && document.write(‘Hello World’); //短路语句
b. || :依次看表达式转换为布尔值的结果,遇真则返回该表达式的值。
c. !
typeof()返回六种数据类型:
number 、 string 、boolean 、undefined 、 object 、 function
console.log(typeof(null)) //object, null为原始值,但是历史遗留问题,当初浏览器用它为对象占位;
var num = 123;
console.log(typeof num) //number, typeof num 这种格式不常用;
console.log(typeof(typeof(num))) //string, typeof()返回值的类型为string
语法分析 ——> 预编译 ——>解释执行
函数体系内的预编译:
1.创建AO对象(Activation Object, 作用域,执行期上下文);
2.找形参和变量声明,将变量和形参作为AO属性名,值为undefined;
3.将实参值和形参统一;
4.在函数体里找函数声明,值赋予函数体。
全局预编译
1.创建GO对象(Global Object, GO === window);
2.找形参和变量声明,将变量和形参作为GO属性名,值为undefined;
3.将实参值和形参统一;
4.找函数声明,值赋予函数体。
未经声明就赋值的变量归全局所有(归GO所有)
正确:
function abc (){
var num = 123;
console.log(num);
}
abc(); //正确
错误:
function abc (){
var num = 123;
console.log(num);
}() //错误, 此为函数声明
表达式定义广泛,除了非表达式,都是表达式!
abc //表达式
123 //表达式
abc = funciton () {} //表达式
i++ //表达式
function test () {} //非表达式,函数声明;
计算符号可以使函数声明变为表达式,如下:
+function test () {} //表达式
-function test () {} //表达式
!function test () {} //表达式
由此可得到立即执行函数(立即执行函数的不规范格式):
// ()让函数声明变为表达式
(function test () {}) //表达式
//即为表达式,便可执行
(function test () {
console.log('a')
})() //正确,可执行
立即执行函数的标准格式和不标准格式:
(function () {} ()) //标准
(function () {})() //不标准
标准格式中的最外层()为数学运算符号,优先级高;{}后面的()为执行符号,优先级低,故和非标准格式一样;
如下表达式被执行后,就会失去对原来函数的索引:
var demo = function () {
console.log('a');
}()
console.log(demo); //undefined
if(function foo (){}){
console.log(typeof foo); //报错
console.log(typeof foo); //undefined
}
但凡可以把function变成表示式的,都会让function失去对自身的索引。
当内部函数被保存到外部时,将会生成闭包。闭包会导致原有作用域链不释放,造成内存泄露。
闭包:
function test(){
var arr = [];
for(var i = 0; i < 10; i++){
arr[i] = function(){
console.log(i);
}
}
return arr;
}
var myArr = test();
for(var j = 0; j < 10; j++){
myArr[j]();
}
//执行结果为console了10个10。因为console.log(i)这个函数并没有被执行,而是保存到了arr中,形成闭包,这个函数的作用域链(AO)没有释放,保存出来的i已经被i++为10
利用立即执行函数和闭包原理解决闭包问题
function test(){
var arr = [];
for(var i = 0; i < 10; i++){
(function (j) {
arr[j] = function(){
console.log(j);
}
}(i))
}
return arr;
}
var myArr = test();
for(var j = 0; j < 10; j++){
myArr[j]()
} //执行结果为console了0,1,2,3,4,5,6,7,8,9
闭包案例:
打印被点击的li的索引
- 1
- 2
- 3
- 4
- 5
错误:
function demo () {
var Lis = document.getElementsByTagName('li');
for(var i = 0; i < Lis.length; i++){
Lis[i].onclick = function(){
console.log(i);
}
}
}
demo();
正确(利用闭包):
function demo () {
var Lis = document.getElementsByTagName('li');
for(var i = 0; i < Lis.length; i++){
(function(j){
Lis[j].onclick = function(){
console.log(j);
}
}(i));
}
}
demo();
正确(利用ES6的let):
function demo (){
var Lis = document.getElementsByTagName("li");
for(let i = 0; i < Lis.length; i++){
Lis[i].onclick = function(){
console.log(i);
}
}
}
demo();
对象有属性,有方法
var andy = {
name: '属性';
health: 100;
run: function (){ //方法
console.log('I like running !');
this.health++;
};
drink: function(){ //方法
console.log('I like drinking !');
this.health--;
}
}
andy.run //表示函数的引用;
andy.run() //表示函数的执行;
1. 对象属性的增、删、改、查
增加属性: andy.wife = 'Nancy';
删除属性: delete andy.drink;
修改属性: andy.wife = 'Kelly';
查询属性: andy.name;
当一个变量没有经过定义使用的话会报错;
当一个对象的属性没有定义便访问的话会打印undefined
2. 对象的创建方法
系统自带的构造函数 new Object()
使用方法如:var obj = new Object();
自定义
function Student ( name, age, sex){
this.name = name;
this.age = age;
this.sex = sex;
this.grade = 2018;
}
var student = new Student( 'Andy', 18, 'male');
构造函数三段式,前提是前面必须加上new
在函数体最前面隐式的加上var this = {};
执行this.xxx = xxx;
隐式的返回this
function Student ( name, age, sex){
this.name = name;
this.age = age;
this.sex = sex;
this.grade = 2018;
}
var student = new Student( 'Andy', 18, 'male');
一旦执行了new,就有如下步骤:
function Student ( name, age, sex){
//1. 在函数体最前面隐式的加上this = {};
// var this = {};
this.name = name;
this.age = age;
this.sex = sex;
this.grade = 2018;
//2. 执行this.xxx = xxx;
//var this = {
// this.name = name;
// this.age = age;
// this.sex = sex;
// this.grade = 2018;
//}
//3. 隐式的返回this
//return this;
}
属性和方法只有对象能有,对象包括对象自己、数组、函数;
原始值是没有属性和方法的。
数字有原始值型数字和对象型数字,字符串、布尔值也是;
对象型的可以有属性和方法。
undefined 、null 不可以有属性和方法。
当一个变量没有经过定义使用的话会报错;
当一个对象的属性没有定义便访问的话会打印undefined
var num = 123; //原始值型
console.log(num);
var num = new Number(123); //对象型数字,可以有属性、对象,也可以参与运算,但是运算结束后又变成原始值型数字
console.log(num);
num = num * 2;
console.log(num)
字符串有个系统自带的属性length。(其实也是系统隐式的进行了包装类)
var str = 'abc;
console.log(str.length); //3
原始值没有属性和方法,但是执行如下代码并不会报错:
var str = 'abc';
str.abc = 'a'; //此时刷新页面,执行不会报错
console.log(str.abc); //undefined(不报错)
var num = 3;
num.len = 5; //此时刷新页面,执行不会报错
console.log(num.len); //undefined(不报错)
以上代码执行时有一过程叫——包装类,如下:
//包装类
var num = 3;
num.len = 5;
//原始值不能有属性,当调用属性时会发生一个隐式的过程
//系统会新建一个数字对象,然后执行上边的属性操作,执行完后会自行消除掉
//new Number(3).len = 5; delete
console.log(num.len);
//当你再次调用这个属性时,系统会再次新建一个数字对象,然后执行属性的访问操作,执行完后再消除掉
//new Number(3).len
//由于构造函数创建的对象是各自独立的,所以这个对象并没有len属性,所以打印结果为undefined
这个隐式的过程叫做包装类。
常见骗术
var arr = [1, 2, 3, 4, 5];
arr.length = 2;
console.log(arr); //[1, 2]
//基于以上结果,如下类似的是否成立
var str = 'abcd';
str.length = 2;
console.log(str);
//结果为'abcd',因为字符串为原始值,执行过程经历了包装类。
案例:
var str = 'abc';
str += 1;
var test = typeof(str);
if(test.length = 6){
test.sign = 'typeof的返回值可能为String';
}
console.log(test.sign);
考题:
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);
console.log(x, y, z) //1, 4, 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)
//A C D
//以下表达式的执行结果是()
parseInt(3, 8)
parseInt(3, 2)
parseInt(3, 0)
//A
3 3 3
//B
3 3 NaN
//C
3 NaN Nan
//D
other
// C和D都可以, parseInt(3, 0)在不同浏览器中执行结果不一样,0为基底的不同执行结果
//封装一个方法,求字符串的字节长度,提示:unicode > 225时字节长度为2,unicode <= 225时字节长度为1
//case 1
function getCodeLen (str) {
var len = 0;
for(var i = 0; i < str.lenth; i++){
if(str.charCodeAt(i) > 225){
len += 2;
}else{
len++;
}
}
return len;
}
//case 2
function getCodeLen2 (str) {
var len = str.length;
for(var i = 0; i < str.length; i++){
if(str.charCodeAt(i) > 225){
len++;
}
}
return len;
}
undefined、null没有包装类。
原型:原型是function对象的一个属性,它定义了构造函数制造出的对象的公共祖先。通过该构造函数产生的对象,可以继承该原型的属性和方法。原型也是对象。
利用原型特点和概念,可以提取共有属性。
对象如何查看原型——>隐式属性__proto__
对象如何查看对象的构造函数——>constructor
原型自己可以增删改,后代不可以对原型进行增删改。后代利用xxx.xxx直接修改算特例,其实相当于原型本身自己改,因为这样直接修改了引用值的堆单元格里的值。(后面有例)
原型是function对象的一个属性,它定义了构造函数制造出的对象的公共祖先。通过该构造函数产生的对象,可以继承该原型的属性和方法。原型也是对象。
例子:
function Student () {
}
//Student.prototype —— 原型
//Student.prototype = {} ——是祖先
Student.prototype.school = '北京大学';
var zhangSan = new Student();
var liSi = new Student();
console.log(zhangSan.school , liSi.school);
原型里有而构造函数本身没有的属性,访问时取原型上的;原型里有而构造函数本身也有的属性,访问时取函数本身的。
Student.prototype.grade = 1;
function Student (){
this.grade = 2;
}
var wangWu = new Student();
console.log(wangWu.grade);
利用原型特点和概念,可以提取共有属性。
Car.prototype.brand = 'BMW';
Car.prototype.lenght = 5200;
Car.prototype.height = 1500;
Car.prototype.width = 1850;
function Car (color, owner){
this.color = color;
this.owner = owner;
}
var car1 = new Car('white', 'Andy');
var car2 = new Car('blue', 'Bale');
console.log(car1.brand, car2.brand);
通过赋值可以修改当前构造函数本身的属性,但是无法修改原型的属性。如修改上例中的属性须使用:Car.prototype.brand = ‘Benz’
在构造函数中对原型的增删改查,只有查可以实现。
原型的简化写法:
Car.prototype = {
brand: 'BMW';
length: 5200;
height: 1500;
width: 1850;
}
function Car (color, owner){
this.color = color;
this.owner = owner;
}
var car1 = new Car();
对象如何查看对象的构造函数——>constructor
function Car (color, owner){
this.color = color;
this. owner = owenr;
}
var car1 = new Car();
//控制台输入
//car1.constructor
//得到Car构造函数
对象的构造函数可以修改:
function Car(color, owner){
this.color = color;
this.owner = owner;
}
function Student(){
this.name = Jack;
}
var car1 = new Car();
//修改方法1
Car.prototype = {
constructor: Student;
}
//修改方法2
car1.constructor = Student;
//控制台输入car1.constructor等到Student函数
对象如何查看原型——>隐式属性_proto_
对于前面的构造函数的三段式:
构造函数三段式,前提是前面必须加上new,1 在函数体最前面隐式的加上var this = {};2. 执行this.xxx = xxx;3. 隐式的返回this
其中第一步中加的var this = {}, 并不是空对象,而是含有__proto__的对象,其中__proto__相当于桥梁、连接指向
Car.prototype.brand = 'BMW';
function Car () {
//var this = {
// __proto__: Car.prototype;
//}
}
var car1 = new Car();
//所以car1.brand可以访问
这个桥梁/连接指向是可以修改的:
var obj = {
brand: 'Benz'
};
Car.prototype.brand = 'BMW';
function Car () {};
var car1 = new Car();
console.log(car1.brand); //结果为BMW
car1.__proto__ = obj;
console.log(car1.brand); //结果为Benz
//但是并没有改变构造函数的原型
var car2 = new Car();
console.log(car2.brand); //结果为BMW
手动修改原型时的问题:
直接用xxx.prototype.xxx = xxx修改
Car.prototype.brand = 'BMW';
function Car () {}
var car1 = new Car();
Car.protoype.brand = 'Benz';
console.log(car1.brand); //结果为Benz
用xxx.prototype = { xxx: xxx}修改
Car.prototype.brand = 'BMW';
function Car () {}
var car2 = new Car();
Car.prototype = {
brand: 'Benz'
}
console.log(car2.brand); //结果为BMW
//修改失败原因如下:
var obj = {name: 'a'}; //引用值
var obj1 = obj;
obj = {name: 'b'};
console.log(obj1); //obj1的值并不会改变
//xxx.prototype = {xxx: xxx}方法亦是如此
var obj = {name: 'a'}; //引用值
var __proto__ = obj;
obj = {name: 'b'};
console.log(__proto__); //__proto__的值并不会改变
//执行步骤:
//Car.prototype.brand = 'BMW';
//function Car () {
// var this = {__proto__: Car.prototype}
//此时__proto__指向的和Car.prototype是一个空间
//}
//var car2 = new Car();
//Car.prototype = {
// brand: 'Benz'
//此时Car.prototype更换了空间,而__proto__指向的空间并没有更换,还是原来的空间,所以值仍为原来的BMW
//}
xxx.prototype.xxx = xxx与xxx.prototype = { xxx: xxx}修改的方法和过程是不一样的,前者直接修改本对象指向的堆中的单元格里的值,后者是在堆中新的单元格中存放一个新的值,然后改变本对象的指针(在栈中存放的堆的单元格编号),使其指向新的单元格。详见原始值与引用值。
函数执行顺序问题,更换xxx.prototype = { xxx: xxx}位置,即可修改成功:
Car.prototype.brand = 'BMW';
function Car () {}
Car.prototype = {
brand: 'Benz'
}
var car2 = new Car();
console.log(car2.brand); //Benz
//函数Car()在new的时候才执行,而此时Benz已经修改了prototype的指针,所以__proto__指向了新的Benz
原型自己可以增删改,后代不可以对原型进行增删改。后代利用xxx.xxx直接修改算特例,其实相当于原型本身自己改,因为这样直接修改了引用值的堆单元格里的值。
原型还可以有原型,原型连成串,就是原型链。
原型链的最顶端是Object.prototype。最终原型
原型链上属性的增删改查
绝大多数对象的最终都会继承自Object.prototype
Object.create(原型)
Person.prototype = {
name: 'a',
sayName: function () {
console.log(this.name);
}
}
function Person () {
this.name = 'b';
}
var person = new Person();
//控制台输入:
//person.name 结果为b
//person.sayName() 结果为b
//sayName()里面的this指向:谁调用这个方法,this就指向谁
sayName()里面的this指向:谁调用这个方法,this就指向谁
Person.prototype = {
height: 100
}
function Person () {
this.eat = function () {
this.height ++;
}
}
var person = new Person();
//控制台输入:
//person.eat() //结果为undefined,因为函数没有设置return,默认undefined
//person
//person.__proto__
对象自变量也有原型;
var obj = {} 也有原型,var的过程系统隐式的进行了var obj = new Object();
Object.create(原型) 也可以创建对象,这个放入的原型必须是对象或者null
//var obj = Object.creat(原型)
var obj = {name: 'sunny', age: 123};
var obj1 = Object.create(obj);
console.log(obj1.name, obj1.age); //sunny 123
//obj为obj1的原型
Person.prototype.name = 'sunny';
function Person () {}
var person = Object.create(Person.prototype);
console.log(person.name); //sunny
特例: Object.create(null)可以创建没有原型的对象。
var obj = Object.create(null);
//控制台输入:
//obj 点开后没有__proto__
//没有原型的对象无法添加原型
控制台输入:
//obj.__proto__ = {name: 'Andy'}
//obj.name 结果为undefined
undefined、null没有原型,没有包装类。
原型链方法重写:原型上有某一方法,自身又写了一个同一名字,不同功能的方法。
有人为重写,有系统自带重写
Object.prototype.toString
Number.prototype.toString //系统自带重写,重写后的方法
Object.prototype.toString.call(123) //原型链顶端中的方法
Arry.prototype.toString //系统自带重写,重写后的方法
Boolean.prototype.toString //系统自带重写,重写后的方法
String.prototype.toString //系统自带重写,重写后的方法
document.write()打印的是toString()方法结果,而不是()中的结果
var obj;
document.write(obj); //undefinded
var obj = Object.create(null);
document.write(obj); //报错。因为Object.create(null)创建的对象没有原型,所以没有toString()方法,故报错
var obj = Object.create(null);
obj.toString = function(){
return "HELLO WORLD";
}
document.write(obj); //HELLO WORLD
javascript 可以计算的数值范围,小数点前16位,小数点后16位
call/apply可以改变this的指向
call 需要把实参按照形参的个数传进去;
apply 需要传一个arguments(数组)
function test () {}
test(); //内部是这样执行的test.call()
var obj = {};
console.log(obj); //undefinded
function Person (name, age) {
this.name = name;
this.age = age;
}
Person.call(obj, 'Andy', 18); //第一个参数obj为改变的this指向,第二个参数为正常传参的第一个参数,后面的参数以此类推
console.log(obj);
实例应用:
//员工A需要一个构造函数Person
funtion Person (name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
//员工B需要一个构造函数Student
function Student (name, age, sex, tel, grade){
this.name = name;
this.age = age;
this.sex = sex;
this.tel = tel;
this.grade = grade;
}
//很明显A和B的函数功能有相同或相似之处,这时可以用call()来精简代码
//员工B的函数可以这样写
function Student (name, age, sex, tel, grade){
Person.call(this, name, age, sex);
this.tel = tel;
this.grade = grade;
}
//call()/apply()基本只有一个功能就是改变this的指向,借以实现借用别人的方法实现自己的功能
应用实例:
//身体=头+颈+躯干+四肢
//Body()=Tou() + Jing() + Qugan() + Sizhi()
function Tou (eye, nose, mouth){
this.eye = eye;
this.nose = nose;
this.mouth = mouth;
}
function Jing (length, coarseness){
this.length = length;
this.coarseness = coarseness;
}
function Qugan (chest, abdomen){
this.chest = chest;
this.abdomen = abdomen;
}
function Sizhi (arm, leg){
this.arm = arm;
this.leg = leg;
}
function Body (eye, nose, mouth, length, coarseness, chest, abdomen, arm, leg){
Tou.call(this, eye, nose, mouth);
Jing.call(this, length, coarseness);
Qugan.call(this, chest, abdomen);
Sizhi.call(this, arm, leg);
}
var body1 = new Body('bule', 'big', 'big', 'short', 'thin', 'plump', 'fat', 'strong', 'short');
console.log(body1);
1、传统形式——>原型链
过多的继承了没用的属性
2、借用构造函数
不能继承借用构造函数的原型
每次构造函数都要多走一个函数
3、共享原型
不能随便改动自己的原型
4、圣杯模式
圣杯模式:
Father.prototype.lastName = 'Ji';
function Father () {}
function F () {}
F.prototype = Father.prototype;
Son.prototype = new F();
function Son () {}
var son = new Son();
console.log(son.lastName); //Ji
//提取一个公式,封装为inherit函数
function inherit (Target, Origin){
function F (){};
F.prototype = Origin.prototype;
Target.prototype = new F();
}
//测试一下封装的函数
Father.prototype.sex = 'male';
function Father () {}
function Son () {}
inherit(Son, Father);
var son = new Son();
var father = new Father();
console.log(son.sex, father.sex); //male male
Son.prototype.like = 'read'; //Son单独设定原型的属性,不会影响原型链上的上代函数
console.log(son.like, father.like); //read undefined
// 扩展
console.log(son.custructor) //并非Son()而是Father()
//son.__proto__ ——> new F()对象身上没有custructor,所以再往上找, new F().__proto__ ——> Father.prototype
管理变量,防止污染全局,适用于模块化开发。
现在几乎不用,现在主要用闭包来解决。
for in
for in循环专门针对对象使用
var obj = {
name: 'Andy,
age: 18,
sex: 'famale',
height: 178,
weight: 75
}
//obj.name等价于obj['name']
console.log(obj.name) //Andy
console.log(obj['name'] //Andy
//遍历对象用for in
for(var key in obj){
console.log(obj.key) //undefined 因为等价于obj['key'],而obj没有为key属性
console.log(obj[key]) //正确,因为key本身就是变量,不用加引号
console.log(obj[key], typeof(obj[key]);
}
for循环可以遍历出原型上手动加的属性,但是遍历不到原型链最顶端的系统的属性
var obj = {
name: 'Andy',
age: 18,
sex: 'male',
height: 180,
weight: 75,
__proto__: {
like: 'football',
love: 'girl',
__proto__: {
class: 'two',
grade: 'three',
__proto__: Object.prototype
}
}
}
Object.prototype.country = 'China';
for(var key in obj){
console.log(obj[key]); //football,girl,two,three,China均可遍历出来
}
hasOwnProperty() 自己的属性,不包括原型上的属性,返回值为布尔值
var obj = {
name: 'Andy',
age: 18,
sex: 'male',
height: 180,
weight: 75,
__proto__: {
like: 'football',
love: 'girl',
__proto__: {
class: 'two',
grade: 'three',
__proto__: Object.prototype
}
}
}
Object.prototype.country = 'China';
for(var key in obj){
if(obj.hasOwnProperty(key)){
console.log(obj[key]); //football,girl,two,three,China均不可遍历出来
}
}
通常for in循环里面配套if循环使用
in 只能判断对象有没有该属性,不能区分是自己的属性还是原型上的属性,返回值为布尔值
对象的属性名为字符串
var obj = {
name: 'Andy',
age: 18,
height: 180,
weight: 75
}
//控制台输入
height in obj //报错,因为没有height变量
'height' in obj //true
A instanceof B 判断A对象是不是B构造函数构造出来的A对象
看A对象的原型链上有没有B的原型
应用:
//判断aaa是数组还是对象,
var aaa;
//方法1
aaa.constructor //数组为function Arry() { [native code] }, 对象为function Object() { [native code] }
//方法2
aaa instanceof Arry //如果是数组返回值为true,如果是对象返回值为false
aaa instanceof Object //如果是对象返回值为true,如果是数组返回值为false
//方法3
Object.prototype.toString = function () {
//谁调用了它,this就指向谁
//1.识别this
//2.返回相应的结果
}
aaa.toString()
//控制台输入
Object.prototype.toString.call(aaa)
//如果是对象,结果为"[object Object]"
//如果是数组,结果为"[object Array]"
,逗号操作符,看一眼逗号前面的,看一眼逗号后面的,然后返回逗号后面的返回。要实现逗号操作符必须用()包起来。
var a = (1, 2); //a的值为2
var a = 1,2 //报错
tips:引用值相比较的是指向的地址,所以:
{} == {} //false
var obj = {};
var obj1 = obj;
obj == obj1 //true
1.函数预编译过程中this——>window
2.全局作用域里this——>window
3.call/apply 可以改变函数运行是this的指向
4.obj.function(); function()里this指向obj
函数在全局环境中被直接调用,严格模式下函数内this指向undefined,非严格模式下函数内this指向window。
var foo = 123;
function print(){
this.foo = 234;
console.log(foo);
}
print() //234
//全局执行时,
//function print (){
// 隐式的进行了this的声明
// var this = window;
//}
new print() //123
//new print()执行时,
//function print(){
// 隐式的进行了this的声明
// var this = Object.create(print.prototype);
//}
new function()会改变function中this的指向
实例:
var bar = {a: '002'};
function print(){
bar.a = 'a';
Object.prototype.b = 'b';
return function inner (){
console.log(bar.a);
console.log(bar.b);
}
}
print()(); //a, b
浅层克隆:
var obj = {
name: 'Andy',
age: 18,
sex: 'female',
card: ['visa', 'unionpay']
}
var obj1 = {};
function clone (original, target){
var target = target || {}; //容错处理,防止调用时不传第二个参数
for(var prop in original){
target[prop] = original[prop];
}
return target;
}
obj1 = clone(obj, obj1);
console.log(obj1);
//此时的为浅层克隆,如源对象中有引用值,则源对象更改时,目标对象也会更改
obj.card.push('master');
console.log(obj1);
深度克隆
步骤:
var obj = {
name: 'Adny',
age: 24,
card: ['visa', 'unionpay'],
wife: {
name: 'Linda',
age: 23,
son: {
name: 'Jacky',
age: 2
}
}
};
var obj1 = {};
console.log(obj1);
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] = {};
}
deepClone(origin[prop], target[prop]);
}else{
target[prop] = origin[prop];
}
}
}
return target;
}
obj1 = deepClone (obj);
console.log(obj1);
obj.card.push('master');
console.log(obj);
console.log(obj1);
条件判断 ? 是 : 否 并且会返回值
比if多一个return,有返回值
var num = 1 > 0 ? 2+2 : 1+1; //4
var num1 = 1 < 0 ? 2+2 : 1+1; //2
数组是特殊的对象
数组的两种产生方法:
数组的读和写:
arr[num] //不可以溢出读(undefined,不会报错)
arr[num] //可以溢出写
数组常用方法
改变原数组:
push,pop,shift,unshift,sort,reverse,splice
不改变原数组:
concat,join ——> split,toString,slice
push()
//自写push()方法
Array.prototype.push = function(){
for(var i = 0; i < arguments.length; i++){
this[this.length] = arguments[i];
}
return this.length;
}
//测试
var arr = [1,2];
arr.push(3,4,5);
console.log(arr);
pop() 把数组最后一位剪切出来,原数组少了一位,且方法return了被剪切掉的数,不接收传参
var arr = [1, 2, 3];
var num = arr.pop();
console.log(num) //3
console.log(arr) //[1, 2]
unshift()在数组前面增加,跟push()方向相反,可以传参
shift()在数组前面剪除,可以传参
reverse()倒序原数组并返回
splice()可以传三个参
arr.splice(从第几位开始,截取多少长度,在切口处添加新的数据)
从第一个参数位开始,截取第二个参数长度的数据(改变原数组,并以数组形式返回截取的数值),在切口处添加第三个参数的内容。
splice()内部是如下处理第一个参数的,所以如果第一个参数传负数,从结果上看就是从倒数开始
funtion splice (pos){
pos += pos > 0 ? 0 : this.length; //判断是否传入负数
//pos >= 0 && pos < this.length 是才有意义
}
sort()给数组排序,改变原数组。根据内容的字符串值排序(ASCII码),并非数字排序
var arr = [1,5,2,10,9,8];
arr.sort() //[1, 10, 2, 5, 8, 9]
所以,sort()提供了接口可以在里面放函数,但是函数有以下规则:
var arr = [1, 2, 10, 15, 35, 3, 4 ,50,15];
arr.sort(function(a,b){
//升序
if(a > b){
return 1;
}else{
return -1;
}
//降序
if(a < b){
return 1;
}else{
return -1;
}
//优化
return a - b //升序
return b - a //降序
})
Math.random()返回0到1之间的开区间数
开区间,不包括两端
闭区间,包括两端
左开右闭,左闭右开,以此类推
给一个有序数组乱序
var arr = [1,2,3,4,5,6,7];
arr.sort(function(a,b){
return Math.random() - 0.5;
});
给一个值为对象的数组排序:
var zhang = {
name: 'zhang',
age: 18,
sex: 'male'
};
var wang = {
name: 'wang',
age: 20,
sex: 'female'
};
var zhao = {
name: 'zhao',
age: 23,
sex: 'female'
};
var arr = [zhang, wang, zhao];
//按照年龄排序
arr.sort(function(a, b){
return a.age - b.age //年龄升序
return b.age - a.age //年龄降序
});
var arr2 = ['sdfasf', 'sa', 'fasfasfsf', 'asgfarhvgfiearhgv', 'rvf'];
//按照字符串长度排序
arr2.sort(function(a, b){
return a.length - b.length;
});
var arr3 = ['1属于', '2张', '32王', 'sfs李', 'sfksfh', 'ds', 'dsas']
//按照字节长度排序
//先封装一个求字节长度的函数returnBytes
function returnBytes (str){
var num = str.length;
for(var i = 0; i < str.length; i++){
if(str.CharCodeAt(i) > 225){
num++;
}
}
return num;
};
arr3.sort(function(a, b){
return returnBytes(a) - returnBytes(b)
});
concat() 连接两个数组,并返回拼接好的新数组,原数组不改变
toString() 把数组变成字符串,并返回,原数组不改变
slice() 截取数组片段,并返回,原数组不改变
可以传0、1、2个参数
传2个参数时:slice(从该位开始截取,截取到该位)
传1个参数时:slice(从该位开始截取且一直截取到最后)
不传参数时:slice()整个截取
join() 传参必须为字符串,将数组内容以传入的字符串连接为一个字符串,并返回,原数组不改变
split() 传参必须为字符串,将字符串内容以传入的字符串拆分为一个数组,并返回,原字符串不改变
join()跟split()实现字符串和数组互相转换
var arr = [1, 2, 3, 4, 5];
var str = arr.join('-');
console.log(arr); //[1, 2, 3, 4, 5]
console.log(str); //1-2-3-4-5
var arr1 = str.split('-');
var arr2 = str.split('3');
console.log(str); //1-2-3-4-5
console.log(arr1); //["1", "2", "3", "4", "5"]
console.log(arr2); //["1-2-", "-4-5"]
是对象,但可以当数组一样用
类数组必要条件:属性要为索引(数字)属性,必须要有length属性,最好加上push
//类数组构建
var obj = {
'0': 'a',
'1': 'b',
'2': 'c',
'length': 3,
'push': Array.prototype.push
}
//调用push方法
obj.push('d');
console.log(obj) //{0: "a", 1: "b", 2: "c", 3: "d", length: 4, push: ƒ}
//实现了对象不可以实现的操作
console.log(typeof(obj)); //object
//再给obj加上splice属性,则obj形状完全同数组一样
var obj = {
'0': 'a',
'1': 'b',
'2': 'c',
'length': 3,
'push': Array.prototype.push,
'splice': Array.prototype.splice
}
console.log(obj); //["a", "b", "c", push: ƒ, splice: ƒ]
console.log(typeof(obj)); //object
例题:
var obj = {
'2': 'a',
'3': 'b',
'length': 2,
'push': Array.prototype.push
}
obj.push('c');
obj.push('d');
//求obj最终的结果
//push方法原理
// Array.prototype.push = function(){
// for(var i = 0; i < arguments.length; i++){
// this[this.length] = arguments[i];
// }
// return this.length;
// }
//答案:{2: "c", 3: "d", length: 4, push: ƒ}
DOM方法能够生成的数组全是类数组
typeof()完善版:
function myTypeof (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(typeof(target) == 'object'){
var str = Object.prototype.toString.call(target);
return template[str];
}else{
return typeof(target);
}
}
数组去重
//数组去重
Array.prototype.unique = function(){
var temp = {},
arr = [],
len = this.length;
for(var i = 0; i < len; i++){
if(!temp[this[i]]){
temp[this[i]] = 'abc';
arr.push(this[i]);
}
}
return arr;
}
var arr = [1,1,2,2,3,3,5,5,4,4];
var arr1 = arr.unique(arr);
通过var生成的window上的属性,叫做不可配置性属性,不可配置性属性delete不掉
复习例题
var name = 'window';
var obj = {
name: 'obj',
say: function(){
console.log(this.name);
}
}
obj.say(); //obj
obj.say.call(); //window ??call()不传参时默认全局调用,相当于window下调用
undefined和null不能跟数进行比较,因为他俩代表的是自己类型的原始值
try里面发生错误,不会执行错误后的try里面的代码,但try外面的后面的代码会继续执行,try里发生错误,但不会抛出错误。catch会收集try里面的错误,传递到形参e里面。
try{
console.log('a');
console.log(b); //发生错误,但是不会抛出错误
console.log('c'); //不会执行
}catch(e){
console.log(e.name + ": " + e.message); //打印错误
}
console.log('d'); //会执行
error.name的六种值对应的信息:
当前浏览器执行js的规则:
基于es3.0 + es5.0的新增方法
es3.0和es5.0产生冲突的部分:
es5.0的严格模式下冲突部分用es5.0,否则用es3.0
“use strict” es5.0严格模式的启动
with(){}改变作用域链
var obj = {
name: 'obj'
};
var name = 'window';
function test (){
var name = 'scope';
with(obj){
console.log(name);
}
}
test(); //obj
//在命名空间中的应用
var org = {
dp1: {
jc: {
name: 'abc',
age: 18
},
deng: {
name: 'efg',
age: 23
}
},
dp2: {
}
}
with(org.dp1.jc){
console.log(name, age);
};
with(org.dp1.deng){
console.log(name, age);
}
查
查看元素节点(get是实时的,query不是实时的)
遍历节点树(元素节点、属性节点、文本节点、注释节点、document、DocumentFragment)
基于元素节点树的遍历(元素节点)
节点的四个属性:nodeName、nodeValue、nodeType、attributes
nodeName: 元素的标签名,以大写形式表示,只读
nodeValue: Text节点或Comment节点的文本内容,可读写
nodeType: 该节点的类型,只读(元素节点1、属性节点2、文本节点3、注释节点8、document9、DocumentFragment11)
attributes: Element节点的属性集合
例:不用children,获取子元素节点
例: 属性节点
节点的一个方法:Node.hasChildNodes()判读节点是否有子节点,返回布尔值
DOM结构树
DOM基本操作:
例:封装函数,返回元素e的第n个兄弟元素节点,n为正,返回后面的兄弟元素节点,n为负,返回前面的,n为0,返回自己
abc
增
插
删
替换
Element节点的一些属性
Element节点的一些方法
例:封装insertAfter函数,功能类似insertBefore
例:秒表
minutes:
seconds:
js不能直接改变css,但是可以间接改变css
读写元素CSS属性
查询计算样式
例: window.getComputedStyle(ele, null)
window.getComputedStyle(ele, ull)可以获取伪元素的css, null传伪元素
window.getComputedStyle(ele, null)在 ie8及ie8以下不兼容,ie自己的方法:
ele.currentStyle //计算样式只读,返回的计算样式的值不是经过转换的绝对值,IE独有的属性
封装兼容性方法getStyle(elem, prop);
例: 封装兼容ie的获取计算样式的方法
function getStyle (elem, prop){
if(window.getComputedStyle){
return window.getComputedStyle(elem, null)[prop];
}else{
return elem.currentStyle[prop];
}
}
例:操作伪元素思想
例: 滑动小方块
如何绑定事件处理函数
例: ele.addEventListener(type, fn, false)
例: obj.attachEvent(‘on’ + type, fn)
例:ele.addEventListener(type, fn, false)
- abc
- abc
- abc
- abc
事件处理程序的运行环境
abc
例:封装兼容性的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);
})
}else{
elem[on + type] = handle;
}
}
//调用时type传参要加引号
解除事件处理程序
事件冒泡
结构上(非视觉上)嵌套关系的元素,会存在事件冒泡的功能,即同一事件,子元素冒泡想父元素。(自底向上)
obj.addEventListener(type, fn, false)实现冒泡
事件捕获
结构上(非视觉上)嵌套关系的元素,会存在事件捕获功能,即同一事件,自父元素捕获至子元素(事件源元素)。(自顶向下)
obj.addEventListener(type, fn, true)实现捕获
IE没有捕获事件
触发顺序:先捕获,后冒泡
focus, blur, change, submit, reset, select等事件不冒泡。
一个对象的一个事件类型只能遵循一种事件处理模型,要么冒泡,要么捕获。
例:冒泡
例: 捕获
例: 捕获和冒泡的触发顺序
取消冒泡
阻止默认事件
默认事件——表单提交、a标签跳转、右键菜单等
event;window.event用于IE //var event = event || window.event
事件源对象:
利用事件冒泡, 和事件源对象进行处理
优点:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
鼠标事件
click、mousedown、mousemove、mouseup、contextmenu、mouseover、 mouseout、mouseenter、mouseleave
用button来区分鼠标的按键:0/1/2
DOM3标准规定: click事件只能监听左键,只能通过mousedown和mouseup来判断鼠标键
如何解决mousedown和click的冲突
例:小方块拖拽
例:mouseenter和mouseleave为新标准,对应的旧标准为mouseover和mouseout,同时绑定则新标准生效
只有mousedown和mouseup可以通过其事件event的button属性来区分左0/中1/右2键
例:
例:区分click和mousedown
例:10秒小游戏
挑战10秒
start
stop
reset
你真特么牛逼!!!
input、focus、blur、change
scroll、 load
js加载的缺点:加载工具方法没必要阻塞文档,过多的js加载会影响页面效率,一旦网速不好,那整个页站将等待js加载而不进行后续渲染等工作。
有些工具方法需要按需加载,用到再加载,不用不加载。
JavaScript异步加载的三种方案
例:
demo.js
function test (){
console.log('abc');
}
demo.html
优化封装:
function loadScript (url, callback){
var script = document.createElement('script');
script.type = 'text/javascript';
if(script.readyState){
script.onreadystatechange = function(){ //IE
if(script.readyState == 'complete' || script.readyState == 'loaded'){
callback();
}
}
}else{
script.onload = function(){ //Safari chrome firefox opera
callback();
}
}
script.src = url; //防止网速过快,瞬间完成下载,而无法触发IE的onreadystatechange,故放在if后面
document.head.appendChild(script);
}
//调用
loadScript ( 'demo.js', function(){
test(); //fn必须放在匿名函数体里面,防止test被当做变量解析,因为loadScript还没有执行,test尚未定义
})
//常规开发中工具函数库一般封装为对象形式
demo.js
var tools = {
test: function(){
console.log('abc');
},
test2: function(){
console.log('def');
},
...
}
//封装:
function loadScript (url, callback){
var script = document.createElement('script');
script.type = 'text/javascript';
if(script.readyState){
script.onreadystatechange = function(){ //IE
if(script.readyState == 'complete' || script.readyState == 'loaded'){
tools[callback]();
}
}
}else{
script.onload = function(){ //Safari chrome firefox opera
tools[callback]();
}
}
script.src = url;
document.head.appendChild(script);
}
//调用:
loadScript('demo.js', 'test');
例:
正则表达式作用:匹配特殊字符或有特殊搭配原则的字符的最佳选择。
正则表达式两种创建方式:
推荐使用直接量
var reg = /abc/m;
var reg1 = new RegExp(reg);
//reg和reg1是两个独立的正则表达式
var reg2 = RegExp(reg)
//reg和reg2是同一个正则表达式,reg2是对reg的一个引用
正则表达式的修饰符(属性)
var reg = /ab/;
var str = 'abc';
var str1 = 'Abc';
reg.test(str); //true
reg.tests(str1); //false
var reg1 = /ab/i;
reg1.test(str); //true
reg1.test(str1); //true
var reg = /ab/;
var str = 'abcabcdabcde';
str.match(reg); //['ab']
var reg1 = /ab/g;
str.match(reg1); //['ab', 'ab', 'ab']
var reg = /ab/m;
var str = 'abc\nabcd\nabcde';
str.match(reg); //['ab']
var reg1 = /^ab/m;
str.match(reg1); //['ab']
var reg2 = /^ab/mg;
str.match(reg2); //['ab', 'ab', 'ab']
正则表达式中[]代表一位, 在[]外面表示以XX开头,在[]里面表示非,跟!一样; |表示或
var reg = /[ab][cd][d]/g;
var str = 'abcd';
console.log(str.match(reg)); //['bcd']
var reg = /[^a][^b]/g;
var str = 'abcde';
console.log(str.match(reg)); //['bc', 'de']
var reg = /(abc|bcd)[0-9]/g;
var str = 'abcd5';
console.log(str.match(reg)); //['bcd5']
例:将the-first-name转换为theFirstName
例:去重‘aabbbcccc’
例: 千分制计数