js面向对象
title: js面向对象
date: 2017年8月17日 18:58:05
updated: 2017年8月27日 09:27:20
面向对象
JavaScript 中的数据类型
值类型和引用类型复制
var foo = 'bar' // 存的是值
var bar = foo
var obj = { // obj 存的是地址
foo: bar
}
var obj1 = obj // obj1 中存储的是和 obj 一样的地址
obj.foo = 'baz' // 地址一样, 指向的内用一样, 所以修改的是同一个对象
console.log(obj, obj1)
值传递和引用传递01.png
-
基本类型数据:
undefined
null
Boolean
Number
String
直接按值存放
基本类型在内存中占据固定大小的空间, 被保存在栈内存中
从一个变量向另一个变量复制基本类型的值 -- 复制的是值的副本 -
引用类型数据: 变量保存的是一个指针, 这个指针地址指向堆内存中的数据.
引用类型的值是对象, 保存在堆内存
保存引用类型的变量保存的并不是对象本身, 而是一个指向该对象的指针
从一个变量向另一个变量复制引用类型的值的时候, 复制的是引用指针, 因此两个变量指向的是同一个对象.
值类型和引用类型参数传递
var a = 123
var b = {
foo: 'bar'
}
function f(a, b) {
a = 456 // var 形参a = 实参a 复制值
b.foo = 'baz' // var 形参b = 实参b 复制引用
b = { // b 中的地址指向新的对象 与 之前的对象断开连接
foo: 'bbb'
}
}
f(a, b)
console.log(a, b) // 123, Object{foo: 'bbb'}
- 基本类型数据: 按值传递
- 引用类型数据: 按引用传递
深拷贝与浅拷贝
-
浅拷贝
当拷贝对象时, 如果属性时对象或者数组, 这时候传递的也只是一个地址. 两者的属性值指向同一内存空间.
var a = { key1:"11111" } function copy(p) { var c = {} for (var i in p) { c[i] = p[i] } return c } a.key2 = ['小辉','小辉'] var b = copy(a) b.key3 = '33333' alert(b.key1) //11111 alert(b.key3) //33333 alert(a.key3) //undefined alert(a.key2) // ['小辉','小辉'] alert(b.key2) // ['小辉','小辉'] b.key2.push("大辉") alert(b.key2) //小辉,小辉,大辉 alert(a.key2) //小辉,小辉,大辉
-
深拷贝
不希望拷贝前后的对象之间有关联, 那么这个时候就会用到深拷贝.
proto.png 关系.pngfunction isObject(obj) { return Object.prototype.toString.call(obj) === '[object Object]' } function isArray(obj) { return Object.prototype.toString.call(obj) === '[object Array]' } // 利用递归实现深拷贝对象复制 function extend(target, source) { for(var key in source) { // 判断如果当前遍历项 source[key] 是一个数组,则先让 target[key] = 数组 // 然后遍历 source[key] 将其中的每一项都复制到 target[key] 中 if (isArray(source[key])) { target[key] = [] // 遍历 source[key] 复制到 target[key] 中 extend(target[key], source[key]) } else if (isObject(source[key])) { target[key] = {} extend(target[key], source[key]) } else { target[key] = source[key] } } }
面向对象 - 基本特性
- 抽象性
-- 只有在具体的环境中对象才可以表示具体的事物
-- 而在程序设计中实际只考虑对象的目标数据 - 封装性
-- 将具体的操作步骤打包起来
-- 使用时无需关心具体的实现过程, 知道怎样使用即可 - 继承性
-- 继承在OOP
中的表现就是扩展. 在原有的对象的基础上, 添加一些新的东西得到新的对象, 这个新的对象就继承自原有的对象. - 多态性
-- 调用同一个方法, 根据传入的参数不同, 得到不同的结果
面向对象 - Error 对象
异常的概念
在代码运行的过程中, 得到与预期不同的结果
处理异常
语法
try { // 需要判断的代码 } catch (e) { // 异常的处理 }
异常对象的传递
代码出现异常, 那么异常后面的代码不再执行, 将错误传递给调用该函数的函数, 直至传到最顶层.
如果有
try - catch
那么出现异常后会执行catch
中异常处理的代码异常对象
在出现异常的时候, 异常出现的位置以及异常的类型, 内容等数据都会被封装起来, 以一个对象的形式传递给
catch
语句中的参数e
,用户可以使用throw 异常对象
抛出异常, 或者new Error(e)
得到异常信息.try { // 需要判断的代码 } catch (e) { console.log(new Error(e)) throw e }
面向对象 - DOM对象
HTML中所有的节点都是对象
<body> 你好, 今天<i>天气很好</i>. <body>
其中:
"你好, 今天" 是一个对象
i 标签也是一个对象
"添加很好" 也是一个对象面向对象 - 继承
原型继承
对象
p
中没有sayHello
方法, 因为构造函数Person
中什么都没有
但是p
连接到原型中了, 因此p
就可以调用sayHello
方法
这就是原型继承,p
没有, 但是p
继承自原型对象, 所以p
有了-
一般的方法
所有的方法写在原型中
function Person ( name, age, gender ) { this.name = name this.age = age this.gender = gender } Person.prototype.sayHello = function () { console.log( '你好, 我是 ' + this.name ) }
-
替换原型
function Person ( name, age, gender ) { this.name = name this.age = age this.gender = gender } Person.prototype = { constructor: Person // 最好手动添加 constructor 的指向 sayHello: function () { }, walk:function () { } } var p = new Person() p.sayHello()
-
不推荐的写法
function Person (name, age, gender) { this.name = name this.age = age this.gender = gender } Person.prototype.sayHello = function () { console.log( '你好, 我是 ' + this.name ) } function Teacher (name, age, gender){ Person.call(this, name, age, gender) } Teacher.prototype = Person.prototype // 不推荐 修改 Teacher.prototype 会修改所有继承自 Person 的对象的原型方法
-
推荐的写法
Teacher.prototype = new Person() // TODO: 执行修改 Teacher.prototype 操作
-
Object.create()
方法Object.create(proto [, propertiesObject ]) 是ES5中提出的一种新的对象创建方式,第一个参数是要继承的原型,如果不是一个子函数,可以传一个null,第二个参数是对象的属性描述符,这个参数是可选的。
function Car (desc) { this.desc = desc this.color = "red" } Car.prototype = { getInfo: function() { return 'A ' + this.color + ' ' + this.desc + '.' } } //instantiate object using the constructor function var car = Object.create(Car.prototype) car.color = "blue" alert(car.getInfo()) // A blue undefined.
propertiesObject
参数的详细解释:(默认都为false)-
writable
: 是否可任意写 -
configurable
:是否能够删除,是否能够被修改 -
enumerable
:是否能用 for in 枚举 -
value:值
访问属性 -
get()
: 访问 -
set()
: 设置
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> </head> <body> <script type="text/javascript"> var obj = { a: function () { console.log(100) }, b: function () { console.log(200) }, c: function () { console.log(300) } } var newObj = {} newObj = Object.create(obj, { t1: { value: 'yupeng', writable: true }, bar: { configurable: false, get: function () { return bar }, set: function (value) { bar = value } } }) console.log(newObj.a()) // 100 console.log(newObj.t1) // yupeng newObj.t1 = 'yupeng1' console.log(newObj.t1) // yupeng1 newObj.bar = 201 console.log(newObj.bar) // 201 function Parent() { } var parent = new Parent() var child = Object.create(parent, { dataDescriptor: { value: "This property uses this string as its value.", writable: true, enumerable: true }, accessorDescriptor: { get: function () { return "I am returning: " + accessorDescriptor }, set: function (val) { accessorDescriptor = val }, configurable: true } }) child.accessorDescriptor = 'YUPENG' console.log(child.accessorDescriptor) // I am returning: YUPENG var Car2 = function () { this.name = 'aaaaaa' } //this is an empty object, like {} Car2.prototype = { getInfo: function () { return 'A ' + this.color + ' ' + this.desc + '.' } } var newCar = new Car2() var car2 = Object.create(newCar, { //value properties color: { writable: true, configurable: true, value: 'red' }, //concrete desc value rawDesc: { writable: true, configurable: true, value: 'Porsche boxter' }, // data properties (assigned using getters and setters) desc: { configurable: true, get: function () { return this.rawDesc.toUpperCase() }, set: function (value) { this.rawDesc = value.toLowerCase() } } }) car2.color = 'blue' console.log(car2.getInfo()) // A blue PORSCHE BOXTER. car2.desc = "XXXXXXXX" console.log(car2.getInfo()) // A blue XXXXXXXX. console.log(car2.name) // aaaaaa </script> </body> </html>
-
混合继承
将原型链和借用构造函数混合使用
function SuperType (name) { this.name = name this.colors = ['red', 'blue', 'green'] } SuperType.prototype.sayName = function () { window.alert(this.name) } function SubType (name, age) { SuperType.call(this, name) this.age = age } // 继承方法 SubType.prototype = new SuperType() SubType.prototype.sayAge = function () { window.alert(this.age) } var instance1 = new SubType('Nicholas', 29) instance1.colors.push('black') window.alert(instance1.colors) // red,blue,green,black instance1.sayName() // Nicholas instance1.sayAge() // 29 var instance2 = new SubType('Greg', 27) window.alert(instance2.colors) // red,blue,green instance2.sayName() // Greg instance2.sayAge() // 27
函数
函数也是对象, 所有的函数都是
Function
的实例.所有函数的
__proto__
都指向Function.prototype
, 包括Function
, 即:function fn () {} fn.__proto__ === Function.prototype // true Function.__proto__ === Function.prototypr /// true
Function.prototype.__proto__ === Object.prototype
函数的参数
length 属性
在javascript中, 创建了一个函数就是创建了一个对象. 函数与一般数据一样使用 -- 赋值, 调用
函数作为对象有一个 length 属性, 改属性用于描述创建函数时参数的个数.arguments 对象
在调用函数的时候, 会给函数参数, 但在函数定义的时候, 有时候不确定要传入多少参数, 所有在调用时传入的参数都会被 arguments 获取到, 也就是说 -- arguments 中存储的是参数的集合
function a () { console.log(arguments.length) } a() // 0 a(1) // 1 a(1,2,3,'4') // 4
如何判断调用时的参数个数与函数定义时的参数个数一样?
函数名.length === arguments.length
将伪数组转为数组
-
声明一个空数组,通过遍历伪数组把它们重新添加到新的数组中
var list = document.querySelectorAll('li') var res = [] list.forEach(function(item, i){ res.push(item) })
伪数组
NodeList
没有forEach
方法, 但是通过document.querySelectorAll()
返回的伪数组有这个方法 -
使用数组的slice()方法 它返回的是数组,使用call或者apply指向伪数组
var res = Array.prototype.slice.call(list)
// 模拟 slice 的实现 Array.prototype.mySlice = function () { // 参数为 0 个, 则从 0 截到最后 // 参数为 1 个, 则从 第一个参数开始的索引 截到最后 // 参数为 2 个, 则从 第一个参数开始的索引 截到第二个参数截止的索引 var start = 0 var end = this.length arguments.length === 1 && (start = arguments[0]) arguments.length === 2 && (start = arguments[0], end = arguments[1]) var tmp = [] for(var i = start; i < end; i++) { tmp.push(this[i]) } return tmp }
-
使用原型继承
list.__proto__ = Array.prototype
-
ES6中数组的新方法 from()
var res = Array.from(list)
-
jq的makeArray(),toArray()方法 它们也可以实现伪数组转数组的功能,但是实现的原理并不太一样
// core_deletedIds = [] // core_slice = core_deletedIds.slice // core_push = core_deletedIds.push // makeArray: 使用了数组的slice方法 toArray: function () { return core_slice.call(this) } // makeArray:使用了push方法 makeArray: function (arr, result) { var ret = result || [] if (arr != null) { if (isArraylike(object(arr))) { jQuery.merge(ret, typeof arr === 'string' ? [arr] : arr ) } else { core_push.call(ret, arr) } } return ret }
callee 属性
-
callee
返回正在执行的函数本身的引用, 他是 arguments 的一个属性arguments.callee === fn // true
-
callee
有一个 length 属性, 可以获得形参的个数. 因此可以用来比较形参与实参个数是否一致.arguments.length === arguments.callee.length
-
可以用来递归匿名函数
var sum = function(n){ if (n <= 1) return 1 else return n + arguments.callee(n - 1) }
caller
caller 返回一个函数的引用, 这个函数调用了当前的函数
使用这个属性要注意:
- 这个属性只有在函数执行时才有作用
- 如果在 javascript 程序中, 函数是顶层调用的, 返回 null
var a = function() { alert(a.caller) } var b = function() { a() } b()
代码中,
b
调用了a
, 所以a.caller
返回的是b
的引用, 结果如下:var b = function() { a() }
如果直接调用
a()
, 输出结果为:null
函数的预解析
在javascript预解析的时候, 同名的函数与变量以函数为准.
已经预解析过得函数, 在代码执行过程中会略过console.log(typeof fn) // function // 在执行阶段,这里对 fn 重新赋值为 123 了 var fn = 123 // 函数声明最大的特性就是:具有函数提升 function fn() { console.log('hello') } console.log(typeof fn) // function number
函数的表达式
函数的表达式类似于变量赋值, 只有变量提升, 没有函数提升, 必须先声明, 再使用
fn() // 报错 VM277:1 Uncaught ReferenceError: fn is not defined console.log(typeof fn) // undefined 函数表达式只有变量提升 var fn = function () { console.log('hello') } fn() // hello
new Function
执行效率低, 很少用.
var add = new Function('x', 'y', 'return x + y') var ret = add(10, 30) console.log(ret)
作用域
块级作用域
所谓的块, 就是代码的逻辑机构, 其中使用
{}
包含的就是语句块. 例如:if (true) { // 语句1 // 语句2 // 语句3 }
这里的
{}
就是语句块块级作用域是指: 从变量定义开始, 到变量所在的语句块结束, 在这样一个范围内可以被使用.
在块级作用域内 本块级的变量可以访问父级块内的变量, 反之不行.
如果子块和父块变量重名, 那么会在定义该变量时隐藏父块中的变量.在子块中定义的变量的改变, 不会影响父块中的变量, 离开子块后, 父块中的变量可以继续使用.
但是, 在javascript中没有块级作用域, 所有声明的变量的作用据就是当前函数范围内, 或者全局.
词法作用域
词法作用域, 指的是变量的访问规则按照词法定义的规则进行使用, 也就是只有函数才可以限定作用域.
访问变量从当前作用域开始往上进行查找 -- 不是代码的书写顺序
问题
var condition = true if (condition) { // var foo = 'bar' function fn() { console.log('hello') } } else { function fn() { console.log('world') } } fn() // 因为在javascript中, 没有块级作用域, 所以 {} 内声明的都是全局 // 低版本(IE10以下)浏览器会先进性函数提升 -- 执行结果是 world
// 对于上面的方式,建议使用函数表达式来处理就可以了 // 因为函数表达式没有函数提升 var condition = true var fn if (condition) { // var foo = 'bar' fn = function () { console.log('hello') } } else { fn = function () { console.log('world') } } fn()
变量的访问规则
函数的作用域链以定义时所处的作用域为准, 而不是调用时.
javascript的执行原理
作用域链
this
this
的指向, 在调用的时候才能确定.调用方式 非严格模式 备注 普通函数调用 window 严格模式下是 undefined 构造函数调用 实例对象 原型方法中 this 也是实例对象 对象方法调用 该方法所属对象 紧挨着的对象 事件绑定方法 绑定事件对象 定时器函数 window 普通函数调用
function a(){ var user = "追梦子" console.log(this.user) //undefined console.log(this) //Window } a()
a()
相当于window.a()
, 在非严格下指向window
可以理解为
window
对象调用对象方法调用
var o = { user:"追梦子", fn:function(){ console.log(this.user) //追梦子 } } o.fn()
this
的指向是在调用的时候确定的, 这里的this
指向对象o
var o = { user:"追梦子", fn:function(){ console.log(this.user) //追梦子 } } window.o.fn()
这里的
this
指向对象o
, 因为o
相当于全局对象window
的一个属性.如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象, 最终要看是哪个对象"点"出来的
var o = { a:10, b:{ a:12, fn:function(){ console.log(this.a) //undefined console.log(this) //window } } } var j = o.b.fn j()
这里的
this
指向并不是b
对象, 虽然函数fn
是被对象b
引用, 但是在将fn
赋值给j
的时候并没有执行, 所以在j
执行的时候,this
的最终指向是window
构造函数调用
function Fn(){ this.user = "追梦子" } var a = new Fn() console.log(a.user) //追梦子
构造函数内的
this
指向构造方法的实例 --a
创建实例时
new
改变了this
的指向使用 new 操作符. 这种方式调用构造函数会经历以下4个阶段: 1.创建一个新对象 2.将构造函数的作用域赋给新对象 -- this 指向新对象 3.执行构造函数中的代码 4.返回新对象
当构造函数内有
return
时:-
return 的值是值类型 不影响 this 指向
-
return 的值是引用类型:
function fn() { this.user = '追梦子' return {} // return 空对象 } var a = new fn console.log(a.user) //undefined
function fn() { this.user = '追梦子' return function(){} // 函数 } var a = new fn console.log(a.user) //undefined
如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例
事件绑定方法
document.getElementById('btn').onclick = function(){ console.log('btn 被点击了') }
事件处理函数不是用户来调用的,而是由系统来调用
事件处理函数内部的 this 指向 DOM 对象
定时器函数
setTimeout(function(){ console.log('时间 +1s') }, 1000)
当时间到达,系统会去帮你调用这个函数, 所以,这个定时器处理函数中的 this 指向的是 window
demo
function Foo(){ getName = function(){ alert(1) } return this } Foo.getName = function (){alert(2)} Foo.prototype.getName=function () {alert(3)} var getName = function (){ alert(4)} function getName(){alert(5)} // 写出下面的结果 // 1 Foo.getName() // 2 getName() // 3 Foo().getName() // 4 new Foo.getName() // 5 new Foo().getName() // 6 new new Foo().getName()
点运算符、new运算符、函数执行这三者之间的优先级:
new A.B(); 的逻辑是这样的:new A.B ();
点运算符优先于new运算符,看起来似乎仅仅如此。
new A().B(); 的逻辑却是这样的:(new A()).B(); 而不是 new (A().B) ();
区别在于A后面多了一对小括号,这个影响到了优先级顺序。//这两种写法是等价的 var d = new A var d = new A() //但是下面这两种是不同的,不能混淆了: var d = new A.B(); //new A.B var d = new A().B(); //new A().B
call & apply
apply
和call
的方法作用是一模一样的,都是用来改变方法的this
关键字,并且把方法执行;而且在严格模式下和非严格模式,对于第一个参数时null/undefined,这样的情况下,也是一样的。// call在给fn传递参数的时候,是一个一个传递值的 call在给fn传递参数的时候,是一个一个传递值的 fn.call(obj, 100, 200) // 而apply不是一个个的传递,而是把要给fn传递的参数值统一的放在一个数组中进行操作 // 但是也相当于一个一个的给fn的参数赋值 fn.apply(obj, [100, 200])
var a = { user:"追梦子", fn:function(){ console.log(this.user) //追梦子 } } var b = a.fn b.call(a)
通过在call方法,给第一个参数添加要把b添加到哪个环境中,简单来说,this就会指向那个对象。
call方法除了第一个参数以外还可以添加多个参数,如下:
var a = { user:"追梦子", fn:function(e,ee){ console.log(this.user) //追梦子 console.log(e+ee) //3 } } var b = a.fn b.call(a,1,2)
注意如果call和apply的第一个参数写的是null,那么this指向的是window对象
var a = { user:"追梦子", fn:function(){ console.log(this) //Window {external: Object, chrome: Object, document: document, a: Object, speechSynthesis: SpeechSynthesis…} } } var b = a.fn b.apply(null)
var keith = { rascal: 123 } var rascal = 456 function a() { console.log(this.rascal) } a() //456 a.call() //456 a.call(null) //456 a.call(undefined) //456 a.call(this) //456 a.call(keith) //123
call
和apply
可以用来借用其他函数的方法var myMath = { max: function () { var max = arguments[0] for (var i = 0; i < arguments.length; i++) { if (arguments[i] > max) { max = arguments[i] } } return max } } var arr = [32, 1, 32, 13, 2, 4321, 13, 21, 3] // 第一个参数用来指定内部 this 的指向 // apply 会把传递的数组或伪数组展开, 一个一个传递到方法内部 var max = myMath.max.apply(null, arr) console.log(max)
bind
call
,apply
, 和bind
的区别:-
call
,apply
, 和bind
都可以改变很熟内部的指向 -
call
,函数apply
在改变this
指向的同时调用函数-
call
通过,
作为分隔进行传参 -
apply
通过传递一个数组或者伪数组传递参数
-
-
bind
改变函数内部this
的指向, 但是他不调用, 而是返回了一个指定了this
环境的新函数
绑定函数
bind() 最简单的用法是创建一个函数, 使这个函数不论怎么调用都有同样的
this
值.常见的错误:
var altwrite = document.write altwrite("hello") //1.以上代码有什么问题 //2.正确操作是怎样的 //3.bind()方法怎么实现
altwrite
改变了this
的指向global
或window
对象, 导致执行时提示非法调用异常Uncaught TypeError: Illegal invocation
, 正确的使用方法:altwrite.bind(document)("hello")
或者
altwrite.call(document, "hello")
常见的错误就像上面的例子一样, 讲方法从对象中拿出来, 然后调用, 并且希望 this 指向原来的对象. 如果不做特殊处理, 一般对象会丢失. 使用 bind() 方法可以很漂亮的解决这个问题.
this.number = 9 var module = { num: 81, getNum: function () { return this.num } } module.getNum() // 81 var getNum = module.getNum getNum() // 9 var boundGetNum = getNum.bind(module) boundGetNum() // 81
偏函数
与 setTimeout 一起使用
一般情况下, setTimeout 的 this 会指向 global 或 window 对象. 当时用类的方法需要 this 指向实例, 就可以用 bind() 将 this 绑定到回调函数来管理实例.
function Bloomer () { this.petalCount = Math.ceil(Math.random() * 12) + 1 } Bloomer.prototype.bloom = function () { window.setTimeout(this.declare.bind(this), 1000) } Bloomer.prototype.declare = function () { console.log(`我有 ${this.petalCount} 朵花瓣`) }
绑定函数作为构造函数
function Point(x, y) { this.x = x this.y = y } Point.prototype.toString = function() { return this.x + ',' + this.y } var p = new Point(1, 2) p.toString() // '1,2' var emptyObj = {} var YAxisPoint = Point.bind(emptyObj, 0/*x*/) // 实现中的例子不支持, // 原生bind支持: var YAxisPoint = Point.bind(null, 0/*x*/) var axisPoint = new YAxisPoint(5) axisPoint.toString() // '0,5' axisPoint instanceof Point // true axisPoint instanceof YAxisPoint // true new Point(17, 42) instanceof YAxisPoint // true
上面例子中Point和YAxisPoint共享原型,因此使用instanceof运算符判断时为true。
捷径
bind()也可以为需要特定this值的函数创造捷径。
例如要讲一个类数组对象转换为真正的数组, 可能会写:
var slice = Array.prototype.slice // ... slice.call(arguments)
使用 bind() 的话:
var unboundSlice = Array.prototype.slice var slice = Function.prototype.call.bind(unboundSlice) // ... slice(arguments)
参数
bind() 可以传参数, 但是不调用
bind() 以后得到的新函数也可以传参, 但在实际使用的时候, 会把在 bind() 时传递的参数和调用新函数传的参数进行合并, 然后作为函数的参数高阶函数
高阶函数就是指:
- 函数可以当做参数进行传递
- 函数可以当做返回值进行返回
作为参数传递
ajax
// callback为待传入的回调函数 var getUserInfo = function(userId, callback) { $.ajax("http://xxx.com/getUserInfo?" + userId, function(data) { if (typeof callback === "function") { callback(data) } }) } getUserInfo(13157, function(data) { alert (data.userName) })
Array.prototype.sort
Array.prototype.sort
接收一个函数作为参数, 这个函数封装了数组元素的排序规则.//从小到大排列 [1, 4, 3].sort(function(a, b) { return a - b }); // 输出: [1, 3, 4] //从大到小排列 [1, 4, 3].sort(function(a, b) { return b - a }); // 输出: [4, 3, 1]
作为返回值返回
判断数据类型
// 以前的代码 function isArray (obj) { return Object.prototype.toString.call(obj) === '[object Array]' } function isObject (obj) { return Object.prototype.toString.call(obj) === '[object Object]' } // ...
// 函数作为返回值的写法 var isArray = generateCheckTypeFn('[object Array]') var isObject = generateCheckTypeFn('[object Object]') var isString = generateCheckTypeFn('[object String]') var isNumber = generateCheckTypeFn('[object Number]') function generateCheckTypeFn (type) { return function (obj) { return Object.propotype.toString.call(obj) === type } } isArray([]) // true isNumber(NaN) // true
闭包
闭包就是能够读取其它函数内部变量的函数
由于在 javascript 中, 只有函数内部的子函数才能读取局部变量, incident可以把闭包简单理解为 "定义在函数内部的函数"
在本质上, 闭包就是将函数内部和函数外部连接起来的一座桥梁.
如果一个函数内部返回了一个函数或者多个函数, 而返回的函数中具有对自己的外层作用域中的成员的读取或者修改, 那么这个函数就成为闭包函数.
如何访问闭包中的数据:
- 利用返回值
- 利用一个对象返回函数
- 返回对象
function fn () { var foo = 'bar' var getFoo = function () { return foo } var setFoo = function (val) { foo = val } return { getFoo: getFoo, setFoo: setFoo } } var obj = fn() console.log(obj.getFoo())
示例代码:
var arr = [10, 20, 30] for(var i = 0; i < arr.length; i++) { arr[i] = (function (i) { return function () { console.log(i) } })(i) } arr.forEach(function (item, index) { item() })
示例代码:
console.log(111) for(var i = 0; i < 3; i++) { // 定时器永远在普通代码的最后执行 // 哪怕时间是 0 setTimeout((function (i) { return function () { console.log(i) } })(i),0) } console.log(222)
沙箱模式
利用匿名函数自执行保护内部成员不被外部修改或者访问
;(function () { var age = 3 function F () { } F.prototype.getAge = function () { return age } F.prototype.setAge = function (val) { if (val < 18) { return console.log('age 不能小于18岁') } age = val } window.F = F })() var f = new F() console.log(f.getAge())
思考题1
var name = 'The Window' var object = { name: "My Object", getNameFunc: function () { return function () { return this.name } } } console.log(object.getNameFunc()())
思考题2
var name = 'The Window' var object = { name: "My Object", getNameFunc: function () { var that = this return function () { return that.name } } } console.log(object.getNameFunc()())
递归
深拷贝
// 判断类型 function getType(type) { return function (obj) { return Object.prototype.toString.call(obj) === `[object ${type}]` } } function isArray(obj) { return getType('Array')(obj) } function isObject(obj) { return getType('Object')(obj) } // 循环拷贝 function deepCopy(target, source) { for (var key in source) { if (source.hasOwnProperty(key)) { var element = source[key]; if (isArray(element)) { target[key] = [] deepCopy(target[key], element) } else if (isObject(element)) { target[key] = {} deepCopy(target[key], element) } else { target[key] = element } } } }
阶乘
function factorial(number) { if (number < 0) { return } else if (number < 2) { return 1 } else { return number * factorial(number - 1) } }
以下代码会导致错误
var fact = factorial fact = null fact(5)
解决办法: 使用
callee
function factorial(number) { if (number < 0) { return } else if (number < 2) { return 1 } else { return number * arguments.callee(number - 1) } } var fact = factorial factorial = null console.log(fact) fact(5)
`callee` 返回正在执行的函数本身的引用, `arguments.callee === fn // true`
- 抽象性