1.1复习函数三种调用方式:普通函数 对象方法 构造函数(理解this关键字作用:谁调用这个函数,this指向谁)
共同特点:this的指向无法动态修改
//1.全局函数
fn();
function fn(){
console.log(this);//window
}
//2.对象的方法
var obj = {
name:'老八',
sayHi:function(){
console.log('奥利给干了兄弟们');
console.log(this);//指向对象{name: "老八", sayHi: ƒ}
}
}
obj.sayHi();
//将fn的地址值赋给sayHi
obj.sayHi = fn;
//此时this指向obj,this指向跟声明没有关系,取决于函数式如何调用的
obj.sayHi();//{name: "班长", sayHi: ƒ}
//3.构造函数
function Person(name,age){
//1.创建一个空对象 2.this指向这个对象 3.执行赋值代码 4.返回这个对象
//this:指向new创建的那个对象
console.log(this);
this.name = name;
this.age = age;
}
var p1 = new Person("萨斯给",19);
var p1 = new Person("哪路拖",21);
//没有加new ,以全局函数方式执行,此时this就是window,函数里面其实是给window添加属性(全局变量)
Person("啊giao",35);
console.log(name);//啊giao
console.log(age);//35
作用:可以动态修改函数中的this指向
函数上下文的三个方法:call()
、apply()
、bind()
它们定义在Function构造函数的原型中
异同点:
函数名.call(this修改后的指向,arg1,arg2…………)
函数名.apply(this修改之后的指向,伪数组或者数组)
函数名.bind(this修改后的指向,arg1,arg2....)
<script>
/*
2.函数上下文模式:
2.1作用:可以动态修改函数中的this
2.2语法:有三种写法,作用都是一样改this,应用场景不同
a.函数名.call(修改的this,arg1,arg2...);
*适用于函数原本形参 <= 1
b.函数名.apply(修改的this,[数组或者伪数组])
*适用于函数原本形参 >= 2
c.函数名.bind(修改的this,arg1,arg2...)
*特点:不会立即执行这个函数,而是返回修改this后的新函数
*适用于不会立即执行的函数:事件处理函数,定时器函数
补充:call() apply() bind() 都是放在Function的prototype原型中
*/
// function fn() {
// //三种执行模式 this无法动态修改
// // this = {age:18};//报错
// console.log(this);
// }
// fn(); //window
/* 上下文模式 */
function fn(a,b){
console.log(this);
console.log(a+b);
}
fn(10,20);//this:window
//a.函数名.call(修改的this,arg1,arg2...)
//应用场景: 适用于函数原本形参 <= 1
fn.call({age:18},10,200);//this:{age: 18}
//b.函数名.apply(修改的this,[数组或者伪数组])
//应用场景:适用于函数原本形参 >= 2
fn.apply({sayHi:function(){console.log('giao');}},[11,22]);//this:{sayHi: ƒ}
//c.函数名.bind(修改的this,arg1,arg2...)
//特点:这个函数不会立即执行,而是返回一个修改this之后的新函数
//应用场景:事件处理函数,定时器
/*
细节:一般用bind()修改this指向,不要修改原本参数
一旦传了,就会变成函数的固定参数,无法修改
一般不传:下一次调用就可以正常传参了
*/
var newFn1= fn.bind({name:'奥利给'},77,3);
newFn1(1,2);//this:{name: "奥利给"} 80
var newFn2 = fn.bind({name:'萨苏给'});
newFn2(22,0);//this:{name:'萨苏给'} 22
//4.定时器中的this一定指向window
//定时器:一段代码间隔时间执行setTimeout(一段代码,间隔时间)
//4.1具名函数
var test = function(){
console.log('我是具名函数');
console.log(this);
};
var newTest = test.bind({name:"张三"});
setTimeout(newTest,1000);
//4.2 匿名函数
setTimeout(function(){
console.log('我是定时器中的函数');
console.log(this);
}.bind({name:'李四'}),2000);
</script>
<script>
/*
1. 函数上下问执行模式 : 动态修改this
注意点 : 修改的this只能是引用类型
2.如果写的是基本数据类型
string,number,boolean : 自定帮我们转成对应的基本装包类型 new String() Boolean() Number()
undefined,null :
*/
function fn(){
console.log(this);
};
fn.call('str');//String
fn.call(1);//Number
fn.call(true);//Boolean
//如果传的是undefined和null,或者不传。代码不会报错,也不会帮我们修改this,还是原来的window
fn.call(undefined);
fn.call(null);
fn.call();
fn.call(window);
</script>
空数组.push.apply(arr,伪数组)
(不修改this指向,利用了apply传参会自动遍历数组/伪数组每一个元素作为实参传递)数组.slice(0)
默认返回数组自身,越过原型链调用slice:Array.prototype.slice.call(weiArr,0)
<script>
/* 需求:伪数组转成真数组,希望伪数组也可以调用数组的api
* 1.伪数组:只有数组的三要素(元素、下标、长度),没有数组的api
* 2.转数组目的:让伪数组也可以调用数组的api
* 3.方式很多种,掌握任何一种即可
*/
var weiArr = {
0: '林绿裙',
1: '保健坤',
2: '泰拳邹',
3: '炮王周',
length: 4
};
// console.log(weiArr);
// console.log(weiArr[0]);
//1.遍历伪数组添加到真数组中
// var arr = [];
// for(var i =0;i<weiArr.length;i++){
// // console.log(weiArr[i]);
// arr[i] = weiArr[i];
// }
// console.log(arr);
// 2.arr.push.apply() 常用
// arr.push(weiArr[0],weiArr[1]....):支持多个参数
// var arr = [];
// //这里使用apply不是为了修改this,所以arr还是不变,而是利用了apply传参会自动遍历数组/伪数组每一个元素作为实参传递
// arr.push.apply(arr,weiArr);//调用push, 使用上下文方式
// console.log(arr);
//3. slice
/*
1.arr.slice(start,end 批量查询 start<=范围<end)
2.如果传入的是0,或者什么都不传会默认返回数组自身
3.如果伪数组可以调用slice,不传参,就会返回一个真数组
*/
//思考:数组可以调用slice,因为数组中的slice方法 存在于原型中
/*
谁可以访问愿心中的成员?
a.每一个实例对象:直接访问 arr.slice() this指向:数组
b.构造函数自身:Array.prototype.slice() this指向:原型
*/
//解决方案:越过原型链,直接从构造函数原型中调用slice方法
weiArr = Array.prototype.slice.call(weiArr,0);//这个0可以写也可以不写
console.log(weiArr);
</script>
方式一:先把伪数组转成真数组,再调用方法:sort
方式二:越过原型链,直接使用Array.prototype.sort()
<script>
var weiArr = {
0:88,
1:24,
2:45,
3:55,
4:90,
length:5
};
//复习数组排序方法sort
// var arr = [2,3,5,1];
// arr.sort(function(a,b){return a-b});
// console.log(arr);
//伪数组如何使用sort排序?
//方式一:先把伪数组转成真数组,再调用sort()
//方式二:越过原型链,直接使用Array.prototype.sort()
Array.prototype.sort.call(weiArr,function(a,b){return a-b});
console.log(weiArr);
</script>
方式一:擂台思想
方式二:通过Math.max()
<script>
//求数组最大值
var arr=[33,91,26,55,77];
//方式一:擂台思想
//方式二:Math.max()
//这里不需要修改this,只是借用apply传参特点:自动遍历数组/伪数组,逐一传参
var max = Math.max.apply(Math,arr);
console.log(max);//91
</script>
之前我们都是通过typeof
检测数据类型,但是我们会发现当我们去检测null或者数组时无法得到准确的结果
为什么数组调用toString和对象调用toString得结果不一样?
<script>
/*
typeof null:能否加测null的数据类型,如果不行,请问如何检测
typeof [10,20,30]:能否检测数组的数据类型,如果不行,请问如何检测
*/
/*
1.检测数据类型:typeof 数据
特点:两种数据类型无法检测 null与array
*/
console.log(typeof null);//object
console.log(typeof [10,20,30]);//object
/*
2.检测数据类型需要使用Object原型Object.prototype中有一个方法toString():
返回固定格式字符串:[object Object]
*/
var obj = {name:"张三"};
console.log(obj.toString());//[object Object]
/* 问题:数组的toString()和Object的toString() 结果不一样
原因:数组的原型也有自己的toString() : 调用数组的join方法返回一个字符串
Object.prototype:toString() 才是返回数据类型
Array.prototype:toString() 本质是调用数组的join()
解决问题:越过原型链,将this指向改为要检测的数据,直接调用Object.prototype.toString();
*/
var arr = [10,20,30];
console.log(arr.toString());//10,20,30
console.log(Object.prototype.toString.call(arr));//[object Array]
/* 3.万能检测数据类型:直接调用Object.prototype中的toStirng() */
console.log(Object.prototype.toString.call("123"));//[object String]
console.log(Object.prototype.toString.call(123));//[object Number]
console.log(Object.prototype.toString.call(true));//[object Boolean]
console.log(Object.prototype.toString.call(undefined));//[object Undefined]
console.log(Object.prototype.toString.call(null));//[object Null]
console.log(Object.prototype.toString.call([1,2,3,4]));//[object Array]
console.log(Object.prototype.toString.call(function(){}));//[object Function]
console.log(Object.prototype.toString.call({}));//[object Object]
</script>
<script>
/*
借用构造函数继承:一个构造函数 去借用 另一个构造函数的复制代码
不常用: 只能继承 this.xxx 成员
*/
//人构造函数
function Person(name,age){
this.name = name;
this.age = age;
// console.log(this);
}
Person.prototype.hobby = "学习";
var p1 = new Person("萨斯给",20);
console.log(p1.hobby);//学习
//学生构造函数
function Student(name,age,score){
// this.name = name;
// this.age = age;
//Person(name,age)
/*
默认情况下:Person(name,age)这个方法里面的this指向的是window
解决方式:Person.call(this,name,age) 修改Person里面的this(window) 为 student函数
*/
Person.call(this,name,age);
this.score = score;
}
</script>
//构造函数
function Person(name,age){
this.name = name;
this.age = age;
};
Person.aaa = '啊啊啊';
console.log(Person.aaa);//静态成员
//实例化对象
var p1 = new Person('张三',20);
console.log(p1.name);//实例成员
console.log(p1.age);//实例成员
Object.prototyp原型中一些常用的属性:
枚举(同时满足两个条件):1.属性是对象自己的成员 2.属性可以使用for-in循环遍历
for-in循环可以遍历的属性有哪些?(1)自己的成员 (2)原型的成员
疑惑:为什么hasOwnProperty与propertyIsEnumerable看起来好像作用一样
数组的length属性属于数组对象的成员,但是for-in循环无法枚举
数组.hasOwnProperty('length') = true
数组.propertyIsEnumerable('length') = false
<script>
/*
1.每一个对象的原型值 最终都会指向Object.prototype(万物皆对象);
2.人以对象都可以通过原型链访问 Object.prototype 里面的成员
3.了解Object.prototype 的常用的成员
*/
console.log(Object.prototype);
//构造函数
function Person(name,age){
this.name = name;
this.age = age;
}
//原型对象
Person.prototype.type = "人类"
//实例对象
var p1 = new Person("老八",45);
//1.对象.hasOwnProperty('属性'):检测对象的属性 是不是自己的
/* 应用场景:检测一个对新娘的属性 是自己的 还是原型的 */
console.log(p1.hasOwnProperty('name'));//true
console.log(p1.hasOwnProperty('sex'));//false
console.log(p1.hasOwnProperty('type'));//false ,type属性不是p1自己的,是原型的
//2.对象A.isPrototypeOf(对象B):检测对象A是不是对象B的原型
//等价于Person.prototype === p1.__proto__
console.log(Person.prototype.isPrototypeOf(p1));//true
console.log(Object.prototype.isPrototypeOf(p1));//true
console.log(Array.prototype.isPrototypeOf(p1));//false
//3.对象.propertyIsEnumerable('属性'):检测对象是否可以枚举某个属性
//枚举必须满足两个条件,缺一不可:(1)属性必须是自己的 (2)属性可以被for-in遍历
console.log(p1.propertyIsEnumerable('name'));//true
//数组的length属性:属于数组,但是无法for-in遍历
var arr = [10,20,30];
console.log(arr.length);
console.log(arr.propertyIsEnumerable('length'));//false 因为无法for-in遍历
for(var key in arr){
console.log('下标'+key);
console.log('元素'+arr[key]);
}
</script>
本小节知识点
函数属于对象,它也会有一些默认属性:
caller
属性:获取调用当前函数的引用(谁调用了我)length
属性:获取函数形参的个数name
属性:获取函数名arguments
属性:获取所有的实参疑惑:arguments关键字 和 函数名.arguments 是否一样?
arguments关键字
:可以理解为是函数中一个默认的形参,作用是存储所有实参,并且与形参一一对应,修改了arguments,形参也会跟着改变(本质是一个对象:伪数组)函数名.arguments
:可以理解为是函数对象的一个属性,作用是获取所有实参,不与形参一一对应,修改了函数名.arguments,形参不会改函数名.arguments == arguments
得到falsearguments对象
arguments
是一个伪数组(拥有数组三要素,没有数组的api,本质是一个对象),有两个常用属性callee
:获取当前函数自身 应用场景:匿名函数递归调用length
:实参的个数 length属于数组的三要素之一<script>
/*
js经典送命题:请说出 call,caller,callee区别
call:属于Function.prototype,作用是修改this指向
caller:属于函数对象,作用是获取调用该函数的引用(我在哪儿被调用了)
callee:属于arguments对象,作用是匿名函数递归调用
函数对象的属性(静态成员)
*/
function fn(a, b) {
console.log(a + b);
}
console.log(fn); //打印函数体代码
console.dir(fn); //打印函数对象的属性
//1.函数名.caller:获取调用函数的引用(我在哪被调用了)
/*
a.如果在函数B中调用函数A:则函数A的caller就是函数B
b.如果在全局作用域中调用函数A,那么函数A的caller是null
*/
function fn1() {
console.log('哈哈');
console.log(fn1.caller); //fn2 我被谁给调用了
}
function fn2() {
console.log('嘻嘻');
//在fn2中调用fn1,那么fn1的caller就是fn2
fn1();
}
fn2();
//在全局作用域中调用fn1,那么fn1的caller是null
fn1();
//2.函数名.lenght:获取函数形参的数量
function fn3(a, b) {
}
console.log(fn3.length); //2
//3.函数名.name:获取函数名
function fn4() {
}
console.log(fn4.name); //'fn4'
//4.函数名.arguments:获取函数所有的实参
function fn5(a, b) {
//函数的静态成员:函数名.arguments
console.log(fn5.arguments);
//关键字:arguments
console.log(arguments);
console.log(fn5.arguments == arguments); //false 数据一样,但是地址不同
console.log();
/*
fn5.arguments是aruments的克隆体 深拷贝
*/
/*
开发中一般使用arguments
arguments.length:实参数量
arguments.callee:获取函数自身
*/
console.log(arguments.length);//3
//唯一应用场景:匿名函数的递归调用(自己调用自己)
console.log(arguments.callee);//fn5
}
fn5(20, 33, 40);
//匿名函数
(function(){
console.log('111');
//自己调用自己
console.log(arguments.callee);//自己
})()
</script>
1.给内置构造函数添加方法
<script>
//1.数组实例对象都是Array构造函数生成
var arr1 = [100, 88, 90, 20, 66];
var arr2 = [-5, -10, 20, 50, 88];
console.log(arr1.__proto__ === arr2.__proto__); //true
console.log(arr1.__proto__ === Array.prototype); //true
//2.内置Arrary构造函数方法不够用,我想自己加入一些自定义方法
//2.1给Array添加冒泡排序方法
Array.prototype.maopao = function(){
//this:调用这个方法的具体数组
for(var i=0;i<this.length-1;i++){
var isSort = true;
for(var j=0;j<this.length-i-1;j++){
if(this[j]>this[j+1]){
isSort = false;
var temp = this[j]
this[j] = this[j+1];
this[j+1] = temp;
}
}
if(isSort){
break;
}
}
return this;
}
console.log(arr1.maopao());// [20, 66, 88, 90, 100]
console.log(arr2.maopao());//[-10, -5, 20, 50, 88]
</script>
2.如何安全的给内置构造函数原型添加方法?
/*1.给内置构造函数原型添加方法弊端
a.存在潜在bug : 多人开发中,每个人都给内置构造函数原型添加方法,就有可能导致变量名一致,产生覆盖的情况
b.资源浪费:你加的方法别人用不上,别人加的方法你用不上
*/
//程序员A
Array.prototype.sayHi = function () {
console.log("哈哈,很帅");
}
//程序员B
Array.prototype.sayHi = function () {
console.log("你帅nm啊...");
}
//程序员A
var arr = [10, 20, 30];
arr.sayHi(); //你帅nm啊...
/*2.解决方案:使用替换原型继承(自定义构造函数,将原型指向内置对象)*/
function myArr() {
this.sayHi = function () {
console.log('奥利给干了兄弟们!');
}
this.maopao = function () {
//this:调用这个方法的具体数组
for (var i = 0; i < this.length - 1; i++) {
var isSort = true;
for (var j = 0; j < this.length - i - 1; j++) {
if (this[j] > this[j + 1]) {
isSort = false;
var temp = this[j]
this[j] = this[j + 1];
this[j + 1] = temp;
}
}
if (isSort) {
break;
}
}
return this;
}
}
/*
这里为什么是空数组而不是Array.prototype呢?
Array.prototype:对象类型赋值的时候拷贝的是地址,修改了myArr的原型之后,Array.prototype也会修改
[]:由于空数组的原型会指向Array.prototype,根据原型链中成员访问规则,myArr实例对象可以访问数组成员的成员
并且,修改myArr的原型对象,本质上是修改这个空数组,不会对Array.prototype造成影响
*/
myArr.prototype = []; //此时MyArr的原型拥有数组对象所有的方法
var arr = new myArr();
arr.push(20, 10, 30,15);
arr.sayHi();
console.log(arr);
console.log(arr.maopao());
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。